From ac9fe0e2ce3a30543aa1901e715dcc921abd86e2 Mon Sep 17 00:00:00 2001 From: danxyu Date: Thu, 9 Jan 2025 10:11:46 -0800 Subject: [PATCH] halfway finished testing e2e --- .../agents/src/agents/token_swap/agent.py | 18 +- .../agents/src/agents/token_swap/tools.py | 61 ++-- .../Imagen/ImageDisplayMessage.module.css} | 0 .../Imagen/ImageDisplayMessage.tsx} | 2 +- .../MorClaims}/ClaimForm.tsx | 0 .../MorClaims}/ClaimMessage.tsx | 2 +- .../index.tsx => Agents/Swaps/Modal.tsx} | 0 .../index.tsx => Agents/Swaps/SwapForm.tsx} | 2 +- .../Swaps/SwapMessage.tsx} | 17 +- .../Agents/Swaps/useSwapTransaction.tsx | 92 ++++++ .../Tweet/TweetMessage.module.css} | 0 .../Tweet/TweetMessage.tsx} | 2 +- .../CDPWallets/CreateWalletForm.tsx | 56 ++++ .../CDPWallets/DeleteWalletDialog.tsx | 72 +++++ .../CDPWallets/RestoreWalletForm.tsx | 42 +++ .../components/CDPWallets/WalletItem.tsx | 74 +++++ .../components/CDPWallets/index.module.css | 291 ++++++++++++++++++ .../frontend/components/CDPWallets/index.tsx | 197 ++++++++++++ .../components/CDPWallets/useWallets.tsx | 290 +++++++++++++++++ .../frontend/components/Chat/index.tsx | 63 +--- .../components/Credentials/Button.tsx | 25 ++ .../CoinbaseConfig.tsx | 0 .../frontend/components/Credentials/Modal.tsx | 161 ++++++++++ .../OneInchConfig.tsx | 0 .../TwitterConfig.tsx | 0 .../frontend/components/HeaderBar/index.tsx | 4 +- .../frontend/components/LeftSidebar/index.tsx | 2 + .../frontend/components/MessageItem/index.tsx | 20 +- .../frontend/components/MessageList/index.tsx | 28 +- .../components/Settings/GeneralSettings.tsx | 57 ++++ .../frontend/components/Settings/index.tsx | 18 +- .../components/Widgets/OneInchSwapWidget.tsx | 138 +++++++++ .../frontend/components/Widgets/index.tsx | 38 ++- .../frontend/pages/index.tsx | 6 +- .../frontend/public/images/coinbase-logo.png | Bin 0 -> 162490 bytes .../frontend/public/images/one-inch-logo.png | Bin 0 -> 24632 bytes .../frontend/public/images/x-logo.jpg | Bin 0 -> 17973 bytes 37 files changed, 1621 insertions(+), 157 deletions(-) rename submodules/moragents_dockers/frontend/components/{ImageDisplay/index.module.css => Agents/Imagen/ImageDisplayMessage.module.css} (100%) rename submodules/moragents_dockers/frontend/components/{ImageDisplay/index.tsx => Agents/Imagen/ImageDisplayMessage.tsx} (93%) rename submodules/moragents_dockers/frontend/components/{ClaimForm => Agents/MorClaims}/ClaimForm.tsx (100%) rename submodules/moragents_dockers/frontend/components/{ClaimMessage => Agents/MorClaims}/ClaimMessage.tsx (92%) rename submodules/moragents_dockers/frontend/components/{SwapAgentModal/index.tsx => Agents/Swaps/Modal.tsx} (100%) rename submodules/moragents_dockers/frontend/components/{SwapForm/index.tsx => Agents/Swaps/SwapForm.tsx} (99%) rename submodules/moragents_dockers/frontend/components/{SwapMessage/index.tsx => Agents/Swaps/SwapMessage.tsx} (65%) create mode 100644 submodules/moragents_dockers/frontend/components/Agents/Swaps/useSwapTransaction.tsx rename submodules/moragents_dockers/frontend/components/{Tweet/index.module.css => Agents/Tweet/TweetMessage.module.css} (100%) rename submodules/moragents_dockers/frontend/components/{Tweet/index.tsx => Agents/Tweet/TweetMessage.tsx} (98%) create mode 100644 submodules/moragents_dockers/frontend/components/CDPWallets/CreateWalletForm.tsx create mode 100644 submodules/moragents_dockers/frontend/components/CDPWallets/DeleteWalletDialog.tsx create mode 100644 submodules/moragents_dockers/frontend/components/CDPWallets/RestoreWalletForm.tsx create mode 100644 submodules/moragents_dockers/frontend/components/CDPWallets/WalletItem.tsx create mode 100644 submodules/moragents_dockers/frontend/components/CDPWallets/index.module.css create mode 100644 submodules/moragents_dockers/frontend/components/CDPWallets/index.tsx create mode 100644 submodules/moragents_dockers/frontend/components/CDPWallets/useWallets.tsx create mode 100644 submodules/moragents_dockers/frontend/components/Credentials/Button.tsx rename submodules/moragents_dockers/frontend/components/{Settings => Credentials}/CoinbaseConfig.tsx (100%) create mode 100644 submodules/moragents_dockers/frontend/components/Credentials/Modal.tsx rename submodules/moragents_dockers/frontend/components/{Settings => Credentials}/OneInchConfig.tsx (100%) rename submodules/moragents_dockers/frontend/components/{Settings => Credentials}/TwitterConfig.tsx (100%) create mode 100644 submodules/moragents_dockers/frontend/components/Settings/GeneralSettings.tsx create mode 100644 submodules/moragents_dockers/frontend/components/Widgets/OneInchSwapWidget.tsx create mode 100644 submodules/moragents_dockers/frontend/public/images/coinbase-logo.png create mode 100644 submodules/moragents_dockers/frontend/public/images/one-inch-logo.png create mode 100644 submodules/moragents_dockers/frontend/public/images/x-logo.jpg diff --git a/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py b/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py index 0af092e..7127a5a 100644 --- a/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py @@ -1,14 +1,22 @@ import logging import requests +from dataclasses import dataclass from src.agents.token_swap import tools from src.models.core import ChatRequest, AgentResponse from src.agents.agent_core.agent import AgentCore from langchain.schema import HumanMessage, SystemMessage from src.stores.key_manager import key_manager_instance +from src.agents.token_swap.config import Config logger = logging.getLogger(__name__) +@dataclass +class TokenSwapContext: + chain_id: str = None + wallet_address: str = None + + class TokenSwapAgent(AgentCore): """Agent for handling token swap operations.""" @@ -16,7 +24,7 @@ def __init__(self, config, llm, embeddings): super().__init__(config, llm, embeddings) self.tools_provided = tools.get_tools() self.tool_bound_llm = self.llm.bind_tools(self.tools_provided) - self.context = [] + self.context = TokenSwapContext() def _api_request_url(self, method_name, query_params, chain_id): base_url = self.config.APIBASEURL + str(chain_id) @@ -53,6 +61,10 @@ async def _process_request(self, request: ChatRequest) -> AgentResponse: content="To help you with token swaps, I need your 1inch API key. Please set it up in Settings first." ) + # Store request context + self.context.chain_id = request.chain_id + self.context.wallet_address = request.wallet_address + messages = [ SystemMessage( content=( @@ -93,8 +105,8 @@ async def _execute_tool(self, func_name: str, args: dict) -> AgentResponse: args["token1"], args["token2"], float(args["value"]), - self.config.chain_id, - self.config.wallet_address, + self.context.chain_id, + self.context.wallet_address, ) # Return an action_required response with swap details diff --git a/submodules/moragents_dockers/agents/src/agents/token_swap/tools.py b/submodules/moragents_dockers/agents/src/agents/token_swap/tools.py index 40f4bb9..2343a5e 100644 --- a/submodules/moragents_dockers/agents/src/agents/token_swap/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/token_swap/tools.py @@ -3,8 +3,11 @@ import requests from src.agents.token_swap.config import Config +from src.stores import key_manager_instance from web3 import Web3 +logger = logging.getLogger(__name__) + class InsufficientFundsError(Exception): pass @@ -18,22 +21,40 @@ class SwapNotPossibleError(Exception): pass -def search_tokens(query, chain_id, limit=1, ignore_listed="false"): +def get_headers() -> dict[str, str]: + """Get headers for 1inch API requests with optional API key override""" + oneinch_keys = key_manager_instance.get_oneinch_keys() + headers = { + "Authorization": f"Bearer {oneinch_keys.api_key}", + "accept": "application/json", + } + return headers + + +def search_tokens( + query: str, + chain_id: int, + limit: int = 1, + ignore_listed: str = "false", +) -> dict | None: + logger.info(f"Searching tokens - Query: {query}, Chain ID: {chain_id}") endpoint = f"/v1.2/{chain_id}/search" - params = {"query": query, "limit": limit, "ignore_listed": ignore_listed} - response = requests.get(Config.INCH_URL + endpoint, params=params, headers=Config.HEADERS) + params = {"query": str(query), "limit": str(limit), "ignore_listed": str(ignore_listed)} + + response = requests.get(Config.INCH_URL + endpoint, params=params, headers=get_headers()) + logger.info(f"Search tokens response status: {response.status_code}") if response.status_code == 200: - return response.json() + result = response.json() + logger.info(f"Found tokens: {result}") + return result else: - logging.error(f"Failed to search tokens. Status code: {response.status_code}") + logger.error(f"Failed to search tokens. Status code: {response.status_code}, Response: {response.text}") return None def get_token_balance(web3: Web3, wallet_address: str, token_address: str, abi: list) -> int: """Get the balance of an ERC-20 token for a given wallet address.""" - if ( - not token_address - ): # If no token address is provided, assume checking ETH or native token balance + if not token_address: # If no token address is provided, assume checking ETH or native token balance return web3.eth.get_balance(web3.to_checksum_address(wallet_address)) else: contract = web3.eth.contract(address=web3.to_checksum_address(token_address), abi=abi) @@ -83,20 +104,26 @@ def validate_swap(web3: Web3, token1, token2, chain_id, amount, wallet_address): raise TokenNotFoundError(f"Token {token2} not found.") # Check if the user has sufficient balance for the swap - if t1_bal < smallest_amount: - raise InsufficientFundsError(f"Insufficient funds to perform the swap.") + # if t1_bal < smallest_amount: + # raise InsufficientFundsError(f"Insufficient funds to perform the swap.") return t1[0]["address"], t1[0]["symbol"], t2[0]["address"], t2[0]["symbol"] def get_quote(token1, token2, amount_in_wei, chain_id): + logger.info(f"Getting quote - Token1: {token1}, Token2: {token2}, Amount: {amount_in_wei}, Chain ID: {chain_id}") endpoint = f"/v6.0/{chain_id}/quote" params = {"src": token1, "dst": token2, "amount": int(amount_in_wei)} - response = requests.get(Config.QUOTE_URL + endpoint, params=params, headers=Config.HEADERS) + logger.debug(f"Quote request - URL: {Config.QUOTE_URL + endpoint}, Params: {params}") + + response = requests.get(Config.QUOTE_URL + endpoint, params=params, headers=get_headers()) + logger.info(f"Quote response status: {response.status_code}") if response.status_code == 200: - return response.json() + result = response.json() + logger.info(f"Quote received: {result}") + return result else: - logging.error(f"Failed to get quote. Status code: {response.status_code}") + logger.error(f"Failed to get quote. Status code: {response.status_code}, Response: {response.text}") return None @@ -104,9 +131,7 @@ def get_token_decimals(web3: Web3, token_address: str) -> int: if not token_address: return 18 # Assuming 18 decimals for the native gas token else: - contract = web3.eth.contract( - address=Web3.to_checksum_address(token_address), abi=Config.ERC20_ABI - ) + contract = web3.eth.contract(address=Web3.to_checksum_address(token_address), abi=Config.ERC20_ABI) return contract.functions.decimals().call() @@ -135,9 +160,7 @@ def swap_coins(token1, token2, amount, chain_id, wallet_address): t2_address = "" if t2_a == Config.INCH_NATIVE_TOKEN_ADDRESS else t2_a t2_quote = convert_to_readable_unit(web3, int(price), t2_address) else: - raise SwapNotPossibleError( - "Failed to generate a quote. Please ensure you're on the correct network." - ) + raise SwapNotPossibleError("Failed to generate a quote. Please ensure you're on the correct network.") return { "dst": t2_id, diff --git a/submodules/moragents_dockers/frontend/components/ImageDisplay/index.module.css b/submodules/moragents_dockers/frontend/components/Agents/Imagen/ImageDisplayMessage.module.css similarity index 100% rename from submodules/moragents_dockers/frontend/components/ImageDisplay/index.module.css rename to submodules/moragents_dockers/frontend/components/Agents/Imagen/ImageDisplayMessage.module.css diff --git a/submodules/moragents_dockers/frontend/components/ImageDisplay/index.tsx b/submodules/moragents_dockers/frontend/components/Agents/Imagen/ImageDisplayMessage.tsx similarity index 93% rename from submodules/moragents_dockers/frontend/components/ImageDisplay/index.tsx rename to submodules/moragents_dockers/frontend/components/Agents/Imagen/ImageDisplayMessage.tsx index 3a0cde0..67c43ca 100644 --- a/submodules/moragents_dockers/frontend/components/ImageDisplay/index.tsx +++ b/submodules/moragents_dockers/frontend/components/Agents/Imagen/ImageDisplayMessage.tsx @@ -1,6 +1,6 @@ import React, { FC } from "react"; import { Box, Flex, Image, Text } from "@chakra-ui/react"; -import styles from "./index.module.css"; +import styles from "./ImageDisplayMessage.module.css"; type ImageDisplayProps = { imageMetadata: { diff --git a/submodules/moragents_dockers/frontend/components/ClaimForm/ClaimForm.tsx b/submodules/moragents_dockers/frontend/components/Agents/MorClaims/ClaimForm.tsx similarity index 100% rename from submodules/moragents_dockers/frontend/components/ClaimForm/ClaimForm.tsx rename to submodules/moragents_dockers/frontend/components/Agents/MorClaims/ClaimForm.tsx diff --git a/submodules/moragents_dockers/frontend/components/ClaimMessage/ClaimMessage.tsx b/submodules/moragents_dockers/frontend/components/Agents/MorClaims/ClaimMessage.tsx similarity index 92% rename from submodules/moragents_dockers/frontend/components/ClaimMessage/ClaimMessage.tsx rename to submodules/moragents_dockers/frontend/components/Agents/MorClaims/ClaimMessage.tsx index fe940d2..7048e78 100644 --- a/submodules/moragents_dockers/frontend/components/ClaimMessage/ClaimMessage.tsx +++ b/submodules/moragents_dockers/frontend/components/Agents/MorClaims/ClaimMessage.tsx @@ -1,5 +1,5 @@ import React, { FC } from "react"; -import { ClaimForm } from "../ClaimForm/ClaimForm"; +import { ClaimForm } from "./ClaimForm"; import { ClaimMessagePayload } from "@/services/types"; type ClaimMessageProps = { diff --git a/submodules/moragents_dockers/frontend/components/SwapAgentModal/index.tsx b/submodules/moragents_dockers/frontend/components/Agents/Swaps/Modal.tsx similarity index 100% rename from submodules/moragents_dockers/frontend/components/SwapAgentModal/index.tsx rename to submodules/moragents_dockers/frontend/components/Agents/Swaps/Modal.tsx diff --git a/submodules/moragents_dockers/frontend/components/SwapForm/index.tsx b/submodules/moragents_dockers/frontend/components/Agents/Swaps/SwapForm.tsx similarity index 99% rename from submodules/moragents_dockers/frontend/components/SwapForm/index.tsx rename to submodules/moragents_dockers/frontend/components/Agents/Swaps/SwapForm.tsx index fc76c82..f2ae4d0 100644 --- a/submodules/moragents_dockers/frontend/components/SwapForm/index.tsx +++ b/submodules/moragents_dockers/frontend/components/Agents/Swaps/SwapForm.tsx @@ -20,7 +20,7 @@ import { } from "@chakra-ui/react"; import { useAccount, useChainId, useReadContract } from "wagmi"; import { erc20Abi, zeroAddress } from "viem"; -import { oneInchNativeToken, routerAddress } from "../../config"; +import { oneInchNativeToken, routerAddress } from "../../../config"; import { InfoIcon } from "@chakra-ui/icons"; export type SwapMessageLike = diff --git a/submodules/moragents_dockers/frontend/components/SwapMessage/index.tsx b/submodules/moragents_dockers/frontend/components/Agents/Swaps/SwapMessage.tsx similarity index 65% rename from submodules/moragents_dockers/frontend/components/SwapMessage/index.tsx rename to submodules/moragents_dockers/frontend/components/Agents/Swaps/SwapMessage.tsx index 9619abd..23beca1 100644 --- a/submodules/moragents_dockers/frontend/components/SwapMessage/index.tsx +++ b/submodules/moragents_dockers/frontend/components/Agents/Swaps/SwapMessage.tsx @@ -1,21 +1,20 @@ import React, { FC } from "react"; -import { SwapForm } from "../SwapForm"; -import { SwapMessagePayload } from "@/services/types"; import { Box, Text } from "@chakra-ui/react"; +import { SwapMessagePayload } from "@/services/types"; +import { SwapForm } from "@/components/Agents/Swaps/SwapForm"; +import { useSwapTransaction } from "@/components/Agents/Swaps/useSwapTransaction"; type SwapMessageProps = { isActive: boolean; - onCancelSwap: (fromAction: number) => void; fromMessage: SwapMessagePayload | null; - onSubmitSwap: (swapTx: any) => void; }; export const SwapMessage: FC = ({ isActive, - onCancelSwap, fromMessage, - onSubmitSwap, }) => { + const { handleSwap, handleCancel, isLoading } = useSwapTransaction(); + if (!fromMessage) { return ( @@ -27,10 +26,10 @@ export const SwapMessage: FC = ({ return ( {}} // Implement approve logic if needed - onSubmitSwap={onSubmitSwap} + onCancelSwap={handleCancel} + onSubmitSwap={handleSwap} + isLoading={isLoading} /> ); }; diff --git a/submodules/moragents_dockers/frontend/components/Agents/Swaps/useSwapTransaction.tsx b/submodules/moragents_dockers/frontend/components/Agents/Swaps/useSwapTransaction.tsx new file mode 100644 index 0000000..5c7c2d6 --- /dev/null +++ b/submodules/moragents_dockers/frontend/components/Agents/Swaps/useSwapTransaction.tsx @@ -0,0 +1,92 @@ +import { useState, useCallback } from "react"; +import { useAccount, useChainId, useSendTransaction } from "wagmi"; +import { sendSwapStatus } from "@/services/apiHooks"; +import { getHttpClient, SWAP_STATUS } from "@/services/constants"; +import { SwapTxPayloadType } from "@/services/types"; + +export const useSwapTransaction = () => { + const [isLoading, setIsLoading] = useState(false); + const [txHash, setTxHash] = useState(""); + const { address } = useAccount(); + const chainId = useChainId(); + const { sendTransaction } = useSendTransaction(); + + const handleSwap = useCallback( + async (swapTx: SwapTxPayloadType) => { + if (!address) return; + + setIsLoading(true); + setTxHash(""); + + try { + sendTransaction( + { + account: address, + data: (swapTx?.tx.data || "0x") as `0x${string}`, + to: (swapTx?.tx.to || "0x") as `0x${string}`, + value: BigInt(swapTx?.tx.value || "0"), + }, + { + onSuccess: async (hash) => { + setTxHash(hash); + await sendSwapStatus( + getHttpClient(), + chainId, + address.toLowerCase(), + SWAP_STATUS.INIT, + hash, + 0 + ); + }, + onError: async (error) => { + console.error(`Error sending transaction: ${error}`); + await sendSwapStatus( + getHttpClient(), + chainId, + address.toLowerCase(), + SWAP_STATUS.FAIL, + "", + 0 + ); + }, + onSettled: () => setIsLoading(false), + } + ); + } catch (error) { + setIsLoading(false); + console.error("Swap failed:", error); + } + }, + [address, chainId, sendTransaction] + ); + + const handleCancel = useCallback( + async (fromAction: number) => { + if (!address) return; + + setIsLoading(true); + try { + await sendSwapStatus( + getHttpClient(), + chainId, + address, + SWAP_STATUS.CANCELLED, + "", + fromAction + ); + } catch (error) { + console.error(`Failed to cancel swap: ${error}`); + } finally { + setIsLoading(false); + } + }, + [address, chainId] + ); + + return { + handleSwap, + handleCancel, + isLoading, + txHash, + }; +}; diff --git a/submodules/moragents_dockers/frontend/components/Tweet/index.module.css b/submodules/moragents_dockers/frontend/components/Agents/Tweet/TweetMessage.module.css similarity index 100% rename from submodules/moragents_dockers/frontend/components/Tweet/index.module.css rename to submodules/moragents_dockers/frontend/components/Agents/Tweet/TweetMessage.module.css diff --git a/submodules/moragents_dockers/frontend/components/Tweet/index.tsx b/submodules/moragents_dockers/frontend/components/Agents/Tweet/TweetMessage.tsx similarity index 98% rename from submodules/moragents_dockers/frontend/components/Tweet/index.tsx rename to submodules/moragents_dockers/frontend/components/Agents/Tweet/TweetMessage.tsx index 6ae9eac..d5f7bd5 100644 --- a/submodules/moragents_dockers/frontend/components/Tweet/index.tsx +++ b/submodules/moragents_dockers/frontend/components/Agents/Tweet/TweetMessage.tsx @@ -21,7 +21,7 @@ import { } from "react-icons/fa"; import { postTweet, regenerateTweet } from "@/services/apiHooks"; import { getHttpClient } from "@/services/constants"; -import styles from "./index.module.css"; +import styles from "./TweetMessage.module.css"; type TweetProps = { initialContent: string; diff --git a/submodules/moragents_dockers/frontend/components/CDPWallets/CreateWalletForm.tsx b/submodules/moragents_dockers/frontend/components/CDPWallets/CreateWalletForm.tsx new file mode 100644 index 0000000..781b1ff --- /dev/null +++ b/submodules/moragents_dockers/frontend/components/CDPWallets/CreateWalletForm.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { + VStack, + FormControl, + FormLabel, + Input, + Select, + Button, +} from "@chakra-ui/react"; +import styles from "./index.module.css"; +interface CreateWalletFormProps { + walletName: string; + selectedNetwork: string; + networks: string[]; + onWalletNameChange: (value: string) => void; + onNetworkChange: (value: string) => void; + onSubmit: () => void; +} + +export const CreateWalletForm: React.FC = ({ + walletName, + selectedNetwork, + networks, + onWalletNameChange, + onNetworkChange, + onSubmit, +}) => ( + + + Wallet Name + onWalletNameChange(e.target.value)} + /> + + + Network + + + + +); diff --git a/submodules/moragents_dockers/frontend/components/CDPWallets/DeleteWalletDialog.tsx b/submodules/moragents_dockers/frontend/components/CDPWallets/DeleteWalletDialog.tsx new file mode 100644 index 0000000..ac1126f --- /dev/null +++ b/submodules/moragents_dockers/frontend/components/CDPWallets/DeleteWalletDialog.tsx @@ -0,0 +1,72 @@ +import React from "react"; +import { + AlertDialog, + AlertDialogOverlay, + AlertDialogContent, + AlertDialogHeader, + AlertDialogBody, + AlertDialogFooter, + Button, + Input, + Text, +} from "@chakra-ui/react"; +import styles from "./index.module.css"; +interface DeleteWalletDialogProps { + isOpen: boolean; + walletId: string; + confirmText: string; + onConfirmChange: (value: string) => void; + onClose: () => void; + onConfirm: () => void; + cancelRef: React.RefObject; +} + +export const DeleteWalletDialog: React.FC = ({ + isOpen, + walletId, + confirmText, + onConfirmChange, + onClose, + onConfirm, + cancelRef, +}) => ( + + + + + Delete Wallet + + + + To confirm deletion, please type the wallet ID:{" "} + + {walletId} + + + onConfirmChange(e.target.value)} + placeholder="Enter wallet ID to confirm" + className={styles.deleteConfirmInput} + /> + + + + + + + + +); diff --git a/submodules/moragents_dockers/frontend/components/CDPWallets/RestoreWalletForm.tsx b/submodules/moragents_dockers/frontend/components/CDPWallets/RestoreWalletForm.tsx new file mode 100644 index 0000000..5964493 --- /dev/null +++ b/submodules/moragents_dockers/frontend/components/CDPWallets/RestoreWalletForm.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import { VStack, FormControl, FormLabel, Button } from "@chakra-ui/react"; +import styles from "./index.module.css"; + +interface RestoreWalletFormProps { + walletFile: File | null; + onFileChange: (file: File) => void; + onSubmit: () => void; +} + +export const RestoreWalletForm: React.FC = ({ + walletFile, + onFileChange, + onSubmit, +}) => ( + + + Upload Wallet File + + + + +); diff --git a/submodules/moragents_dockers/frontend/components/CDPWallets/WalletItem.tsx b/submodules/moragents_dockers/frontend/components/CDPWallets/WalletItem.tsx new file mode 100644 index 0000000..dd35a32 --- /dev/null +++ b/submodules/moragents_dockers/frontend/components/CDPWallets/WalletItem.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import { HStack, VStack, Text, Button } from "@chakra-ui/react"; +import { CopyIcon, StarIcon, DownloadIcon, DeleteIcon } from "@chakra-ui/icons"; +import styles from "./index.module.css"; + +interface WalletItemProps { + wallet: { + wallet_id: string; + network_id: string; + address: string; + }; + isActive: boolean; + onCopy: (address: string) => void; + onSetActive: (id: string) => void; + onDownload: (id: string) => void; + onDelete: (id: string) => void; +} + +export const WalletItem: React.FC = ({ + wallet, + isActive, + onCopy, + onSetActive, + onDownload, + onDelete, +}) => ( +
+ + {wallet.wallet_id} + Network: {wallet.network_id} + {wallet.address} + + + + + + + +
+); diff --git a/submodules/moragents_dockers/frontend/components/CDPWallets/index.module.css b/submodules/moragents_dockers/frontend/components/CDPWallets/index.module.css new file mode 100644 index 0000000..f42ef9b --- /dev/null +++ b/submodules/moragents_dockers/frontend/components/CDPWallets/index.module.css @@ -0,0 +1,291 @@ +/* Menu Button */ +.menuButton { + background-color: rgba(255, 255, 255, 0.05) !important; + color: white !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + padding: 12px 20px !important; + border-radius: 8px !important; + transition: all 0.2s ease !important; + font-weight: 500 !important; + min-width: 160px !important; +} + +.menuButton:hover { + background-color: rgba(255, 255, 255, 0.1) !important; + border-color: rgba(255, 255, 255, 0.2) !important; + transform: translateY(-1px); +} + +/* Menu List */ +.menuList { + background: #141414 !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3) !important; + padding: 8px !important; + min-width: 360px !important; + border-radius: 12px !important; +} + +/* Create Button Section */ +.createButtonWrapper { + padding: 12px !important; +} + +.createButton { + width: 100% !important; + background-color: rgba(52, 211, 153, 0.15) !important; + color: rgb(52, 211, 153) !important; + border: 1px solid rgba(52, 211, 153, 0.2) !important; + padding: 12px !important; + border-radius: 8px !important; + transition: all 0.2s ease !important; + font-weight: 500 !important; +} + +.createButton:hover { + background-color: rgba(52, 211, 153, 0.2) !important; + border-color: rgba(52, 211, 153, 0.3) !important; + transform: translateY(-1px); +} + +/* Divider */ +.divider { + border-color: rgba(255, 255, 255, 0.1) !important; + margin: 8px 0 !important; +} + +/* Wallet Item */ +.walletItem { + padding: 16px !important; + border-radius: 8px !important; + background-color: rgba(255, 255, 255, 0.02) !important; + margin: 4px 0 !important; + transition: all 0.2s ease !important; + display: flex !important; + justify-content: space-between !important; + align-items: center !important; +} + +.walletItem:hover { + background-color: rgba(255, 255, 255, 0.05) !important; + transform: translateX(2px); +} + +/* Wallet Info */ +.walletInfo { + flex: 1; + margin-right: 16px; +} + +.walletName { + color: white !important; + font-weight: 500 !important; + font-size: 15px !important; +} + +.networkId { + color: rgba(255, 255, 255, 0.6) !important; + font-size: 13px !important; +} + +.address { + color: rgba(255, 255, 255, 0.4) !important; + font-size: 12px !important; + font-family: monospace !important; +} + +/* Action Buttons */ +.actionButtons { + opacity: 0; + transition: opacity 0.2s ease !important; +} + +.walletItem:hover .actionButtons { + opacity: 1; +} + +.actionButton { + background: transparent !important; + color: rgba(255, 255, 255, 0.6) !important; + padding: 8px !important; + border-radius: 6px !important; + transition: all 0.2s ease !important; + width: 32px !important; + height: 32px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; +} + +.actionButton:hover { + background-color: rgba(255, 255, 255, 0.1) !important; + color: white !important; + transform: scale(1.1); +} + +.copyButton:hover { + color: #4299e1 !important; +} + +.starButton.active { + color: #ecc94b !important; +} + +.starButton:hover { + color: #ecc94b !important; +} + +.downloadButton:hover { + color: #48bb78 !important; +} + +.deleteButton:hover { + color: #f56565 !important; +} + +/* Empty State */ +.emptyState { + padding: 24px !important; + text-align: center !important; + color: rgba(255, 255, 255, 0.4) !important; +} + +/* Modal Styles */ +.modalContent { + background: #141414 !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + border-radius: 12px !important; +} + +.modalHeader { + color: white !important; + border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; + padding: 20px 24px !important; +} + +.modalBody { + padding: 24px !important; +} + +/* Tabs */ +.tabs { + border-color: rgba(255, 255, 255, 0.1) !important; +} + +.tabList { + border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; +} + +.tab { + color: rgba(255, 255, 255, 0.6) !important; + border-bottom: 2px solid transparent !important; + transition: all 0.2s ease !important; +} + +.tab[aria-selected="true"] { + color: white !important; + border-bottom-color: rgb(52, 211, 153) !important; +} + +/* Forms */ +.formLabel { + color: rgba(255, 255, 255, 0.8) !important; + font-size: 14px !important; + margin-bottom: 8px !important; +} + +.input, +.select { + background-color: rgba(255, 255, 255, 0.05) !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + color: white !important; + transition: all 0.2s ease !important; +} + +.input:hover, +.select:hover { + border-color: rgba(255, 255, 255, 0.2) !important; +} + +.input:focus, +.select:focus { + border-color: rgb(52, 211, 153) !important; + box-shadow: 0 0 0 1px rgb(52, 211, 153) !important; +} + +.fileUploadButton { + width: 100% !important; + background-color: rgba(255, 255, 255, 0.05) !important; + color: rgba(255, 255, 255, 0.8) !important; + border: 1px dashed rgba(255, 255, 255, 0.2) !important; + padding: 24px !important; + border-radius: 8px !important; + transition: all 0.2s ease !important; +} + +.fileUploadButton:hover { + background-color: rgba(255, 255, 255, 0.08) !important; + border-color: rgba(255, 255, 255, 0.3) !important; +} + +.submitButton { + width: 100% !important; + background-color: rgb(52, 211, 153) !important; + color: #141414 !important; + padding: 12px !important; + border-radius: 8px !important; + font-weight: 500 !important; + transition: all 0.2s ease !important; +} + +.submitButton:hover { + background-color: rgb(72, 231, 173) !important; + transform: translateY(-1px); +} + +/* Delete Dialog */ +.deleteDialog { + background: #141414 !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + border-radius: 12px !important; +} + +.deleteDialogHeader { + color: white !important; + border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; +} + +.deleteDialogBody { + padding: 24px !important; +} + +.deleteConfirmText { + color: rgba(255, 255, 255, 0.8) !important; + margin-bottom: 16px !important; +} + +.deleteConfirmInput { + background-color: rgba(255, 255, 255, 0.05) !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + color: white !important; +} + +.deleteDialogFooter { + border-top: 1px solid rgba(255, 255, 255, 0.1) !important; + padding: 16px 24px !important; +} + +.cancelButton { + background-color: rgba(255, 255, 255, 0.05) !important; + color: white !important; +} + +.confirmDeleteButton { + background-color: rgb(245, 101, 101) !important; + color: white !important; + margin-left: 12px !important; +} + +.confirmDeleteButton:hover { + background-color: rgb(229, 62, 62) !important; +} diff --git a/submodules/moragents_dockers/frontend/components/CDPWallets/index.tsx b/submodules/moragents_dockers/frontend/components/CDPWallets/index.tsx new file mode 100644 index 0000000..4e65b86 --- /dev/null +++ b/submodules/moragents_dockers/frontend/components/CDPWallets/index.tsx @@ -0,0 +1,197 @@ +// CDPWallets.tsx +import React, { useState, useEffect } from "react"; +import { + Button, + Menu, + MenuButton, + MenuList, + Box, + Text, + useDisclosure, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalCloseButton, + Tabs, + TabList, + TabPanels, + Tab, + TabPanel, + Divider, +} from "@chakra-ui/react"; +import { ChevronDownIcon } from "@chakra-ui/icons"; +import { useWallets, NETWORKS } from "./useWallets"; +import { WalletItem } from "./WalletItem"; +import { CreateWalletForm } from "./CreateWalletForm"; +import { RestoreWalletForm } from "./RestoreWalletForm"; +import { DeleteWalletDialog } from "./DeleteWalletDialog"; +import styles from "./index.module.css"; + +export const CDPWallets: React.FC = () => { + // Form state + const [newWalletName, setNewWalletName] = useState(""); + const [selectedNetwork, setSelectedNetwork] = useState(NETWORKS[0]); + const [walletFile, setWalletFile] = useState(null); + const [walletToDelete, setWalletToDelete] = useState(""); + const [confirmWalletId, setConfirmWalletId] = useState(""); + + // Dialog management + const { isOpen, onOpen, onClose } = useDisclosure(); + const { + isOpen: isDeleteOpen, + onOpen: onDeleteOpen, + onClose: onDeleteClose, + } = useDisclosure(); + const { + isOpen: isMenuOpen, + onClose: closeMenu, + onOpen: openMenu, + } = useDisclosure(); + const cancelRef = React.useRef(null); + + // Wallet operations + const { + wallets, + activeWallet, + fetchWallets, + handleCopyAddress, + handleSetActiveWallet, + handleCreateWallet, + handleRestoreWallet, + handleDownloadWallet, + handleDeleteWallet, + } = useWallets(); + + // Initial fetch + useEffect(() => { + fetchWallets(); + }, [fetchWallets]); + + // Form submission handlers + const onCreateWallet = async () => { + const success = await handleCreateWallet(newWalletName, selectedNetwork); + if (success) { + onClose(); + setNewWalletName(""); + } + }; + + const onRestoreWallet = async () => { + if (!walletFile) return; + const success = await handleRestoreWallet(walletFile); + if (success) { + onClose(); + setWalletFile(null); + } + }; + + const onDeleteWallet = async () => { + const success = await handleDeleteWallet(walletToDelete, confirmWalletId); + if (success) { + onDeleteClose(); + setConfirmWalletId(""); + setWalletToDelete(""); + } + }; + + return ( + <> + + } + className={styles.menuButton} + > + CDP Wallets + + + + + + + {wallets.length > 0 ? ( + wallets.map((wallet) => ( + { + setWalletToDelete(id); + onDeleteOpen(); + }} + /> + )) + ) : ( + + No wallets created yet + + )} + + + + + + + + CDP Wallet Management + + + + + + Create New + Restore Existing + + + + + + + + + + + + + + + { + onDeleteClose(); + setConfirmWalletId(""); + setWalletToDelete(""); + }} + onConfirm={onDeleteWallet} + cancelRef={cancelRef} + /> + + ); +}; diff --git a/submodules/moragents_dockers/frontend/components/CDPWallets/useWallets.tsx b/submodules/moragents_dockers/frontend/components/CDPWallets/useWallets.tsx new file mode 100644 index 0000000..adb3920 --- /dev/null +++ b/submodules/moragents_dockers/frontend/components/CDPWallets/useWallets.tsx @@ -0,0 +1,290 @@ +import { useState, useCallback } from "react"; +import { useToast } from "@chakra-ui/react"; + +interface Wallet { + wallet_id: string; + network_id: string; + address: string; +} + +interface UseWalletsReturn { + wallets: Wallet[]; + activeWallet: string | null; + fetchWallets: () => Promise; + handleCopyAddress: (address: string) => void; + handleSetActiveWallet: (walletId: string) => Promise; + handleCreateWallet: (walletName: string, network: string) => Promise; + handleRestoreWallet: (walletFile: File) => Promise; + handleDownloadWallet: (walletId: string) => Promise; + handleDeleteWallet: ( + walletId: string, + confirmWalletId: string + ) => Promise; +} + +export const useWallets = (): UseWalletsReturn => { + const [wallets, setWallets] = useState([]); + const [activeWallet, setActiveWallet] = useState(null); + const toast = useToast(); + + const fetchWallets = useCallback(async () => { + try { + const [walletsResponse, activeWalletResponse] = await Promise.all([ + fetch("http://localhost:8080/wallets/list"), + fetch("http://localhost:8080/wallets/active"), + ]); + + const walletsData = await walletsResponse.json(); + const activeData = await activeWalletResponse.json(); + + setWallets(walletsData.wallets || []); + setActiveWallet(activeData.active_wallet_id); + } catch (error) { + console.error("Failed to fetch wallets:", error); + toast({ + title: "Error fetching wallets", + status: "error", + duration: 3000, + }); + } + }, [toast]); + + const handleCopyAddress = (address: string) => { + navigator.clipboard.writeText(address); + toast({ + title: "Address copied to clipboard", + status: "success", + duration: 2000, + }); + }; + + const handleSetActiveWallet = async (walletId: string) => { + try { + const response = await fetch("http://localhost:8080/wallets/active", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + wallet_id: walletId, + }), + }); + + if (response.ok) { + setActiveWallet(walletId); + toast({ + title: "Active wallet set successfully", + status: "success", + duration: 3000, + }); + } else { + const errorData = await response.json(); + throw new Error(errorData.message || "Failed to set active wallet"); + } + } catch (error) { + console.error("Error setting active wallet:", error); + toast({ + title: + error instanceof Error + ? error.message + : "Failed to set active wallet", + status: "error", + duration: 3000, + }); + } + }; + + const handleCreateWallet = async (walletName: string, network: string) => { + if (!walletName.trim()) { + toast({ + title: "Please enter a wallet name", + status: "warning", + duration: 3000, + }); + return; + } + + try { + const response = await fetch("http://localhost:8080/wallets/create", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + wallet_id: walletName, + network_id: network, + set_active: true, + }), + }); + + if (response.ok) { + toast({ + title: "Wallet created successfully", + status: "success", + duration: 3000, + }); + await fetchWallets(); + } else { + const errorData = await response.json(); + throw new Error(errorData.message || "Failed to create wallet"); + } + } catch (error) { + console.error("Error creating wallet:", error); + toast({ + title: + error instanceof Error ? error.message : "Failed to create wallet", + status: "error", + duration: 3000, + }); + } + }; + + const handleRestoreWallet = async (walletFile: File) => { + try { + const fileContent = await walletFile.text(); + const walletData = JSON.parse(fileContent); + + const response = await fetch("http://localhost:8080/wallets/restore", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + wallet_id: walletData.wallet_id, + wallet_data: walletData, + set_active: true, + }), + }); + + if (response.ok) { + toast({ + title: "Wallet restored successfully", + status: "success", + duration: 3000, + }); + await fetchWallets(); + } else { + const errorData = await response.json(); + throw new Error(errorData.message || "Failed to restore wallet"); + } + } catch (error) { + console.error("Error restoring wallet:", error); + toast({ + title: + error instanceof Error ? error.message : "Failed to restore wallet", + status: "error", + duration: 3000, + }); + } + }; + + const handleDownloadWallet = async (walletId: string) => { + try { + const response = await fetch( + `http://localhost:8080/wallets/export/${walletId}` + ); + + if (response.ok) { + const data = await response.json(); + if (data.status === "success") { + const blob = new Blob([JSON.stringify(data.data, null, 2)], { + type: "application/json", + }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `${walletId}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + + toast({ + title: "Wallet exported successfully", + status: "success", + duration: 3000, + }); + } else { + throw new Error(data.message || "Failed to export wallet"); + } + } else { + const errorData = await response.json(); + throw new Error(errorData.message || "Failed to export wallet"); + } + } catch (error) { + console.error("Error exporting wallet:", error); + toast({ + title: + error instanceof Error ? error.message : "Failed to export wallet", + status: "error", + duration: 3000, + }); + } + }; + + const handleDeleteWallet = async ( + walletId: string, + confirmWalletId: string + ) => { + if (confirmWalletId !== walletId) { + toast({ + title: "Wallet ID does not match", + status: "error", + duration: 3000, + }); + return false; + } + + try { + const response = await fetch( + `http://localhost:8080/wallets/${walletId}`, + { + method: "DELETE", + } + ); + + if (response.ok) { + toast({ + title: "Wallet deleted successfully", + status: "success", + duration: 3000, + }); + await fetchWallets(); + return true; + } else { + const errorData = await response.json(); + throw new Error(errorData.message || "Failed to delete wallet"); + } + } catch (error) { + console.error("Error deleting wallet:", error); + toast({ + title: + error instanceof Error ? error.message : "Failed to delete wallet", + status: "error", + duration: 3000, + }); + return false; + } + }; + + return { + wallets, + activeWallet, + fetchWallets, + handleCopyAddress, + handleSetActiveWallet, + handleCreateWallet, + handleRestoreWallet, + handleDownloadWallet, + handleDeleteWallet, + }; +}; + +export const NETWORKS = [ + "base-mainnet", + "base-sepolia", + "base-goerli", + "ethereum-mainnet", + "ethereum-goerli", + "ethereum-sepolia", +] as const; diff --git a/submodules/moragents_dockers/frontend/components/Chat/index.tsx b/submodules/moragents_dockers/frontend/components/Chat/index.tsx index 4292b1d..e2080f8 100644 --- a/submodules/moragents_dockers/frontend/components/Chat/index.tsx +++ b/submodules/moragents_dockers/frontend/components/Chat/index.tsx @@ -1,46 +1,25 @@ import React, { FC, useEffect, useState } from "react"; import { Flex, Box } from "@chakra-ui/react"; import { ChatMessage } from "@/services/types"; -import { useTransactionConfirmations } from "wagmi"; +// import { useTransactionConfirmations } from "wagmi"; import { MessageList } from "@/components/MessageList"; import { ChatInput } from "@/components/ChatInput"; import { LoadingIndicator } from "@/components/LoadingIndicator"; import { Widgets, shouldOpenWidget } from "@/components/Widgets"; import { ChatProps } from "@/components/Chat/types"; -import { useChat } from "@/components/Chat/hooks"; export const Chat: FC = ({ onSubmitMessage, - onCancelSwap, messages, - onBackendError, - // New prop from Home that indicates whether the sidebar is open isSidebarOpen = false, }) => { const [messagesData, setMessagesData] = useState(messages); const [activeWidget, setActiveWidget] = useState(null); const [isWidgetOpen, setIsWidgetOpen] = useState(false); - - const { - txHash, - approveTxHash, - showSpinner, - setShowSpinner, - handleSwapSubmit, - handleClaimSubmit, - } = useChat(onBackendError); - - useTransactionConfirmations({ - hash: (txHash || "0x") as `0x${string}`, - }); - - useTransactionConfirmations({ - hash: (approveTxHash || "0x") as `0x${string}`, - }); + const [isLoading, setIsLoading] = useState(false); useEffect(() => { if (messages.length > 0) { - console.log("messages", messages); const lastMessage = messages[messages.length - 1]; if (lastMessage.role === "assistant" && shouldOpenWidget(lastMessage)) { setActiveWidget(lastMessage); @@ -54,21 +33,11 @@ export const Chat: FC = ({ }, [messages]); const handleSubmit = async (message: string, file: File | null) => { - setShowSpinner(true); + setIsLoading(true); await onSubmitMessage(message, file); - setShowSpinner(false); - }; - - const handleCloseWidget = () => { - setIsWidgetOpen(false); - setActiveWidget(null); + setIsLoading(false); }; - // Decide how far from the left we want to be when the sidebar is open vs closed. - // Example: if the sidebar is fully open, let's shift it 280px to the right. - // If it's closed, maybe shift only 80px from the edge. Tweak as needed. - const chatMarginLeft = isSidebarOpen ? "280px" : "80px"; - return ( = ({ width="100%" transition="all 0.3s ease-in-out" mt={4} - // Existing widget-based padding logic paddingLeft={isWidgetOpen ? "5%" : "10%"} paddingRight={isWidgetOpen ? "35%" : "30%"} - // NEW MARGIN to keep space from the sidebar - ml={chatMarginLeft} + ml={isSidebarOpen ? "280px" : "80px"} > - - {showSpinner && } + + {isLoading && } 1} - disabled={ - showSpinner || - messagesData[messagesData.length - 1]?.role === "swap" - } + disabled={isLoading} isSidebarOpen={isSidebarOpen} /> - {/* The widgets panel on the right side */} = ({ borderLeft="1px solid gray" zIndex={1} > - + setIsWidgetOpen(false)} + /> ); diff --git a/submodules/moragents_dockers/frontend/components/Credentials/Button.tsx b/submodules/moragents_dockers/frontend/components/Credentials/Button.tsx new file mode 100644 index 0000000..04c013d --- /dev/null +++ b/submodules/moragents_dockers/frontend/components/Credentials/Button.tsx @@ -0,0 +1,25 @@ +import { useState } from "react"; +import { Button } from "@chakra-ui/react"; +import { FaLock } from "react-icons/fa"; +import { ApiCredentialsModal } from "./Modal"; + +export const ApiCredentialsButton: React.FC = () => { + const [isOpen, setIsOpen] = useState(false); + + return ( + <> + + + setIsOpen(false)} /> + + ); +}; diff --git a/submodules/moragents_dockers/frontend/components/Settings/CoinbaseConfig.tsx b/submodules/moragents_dockers/frontend/components/Credentials/CoinbaseConfig.tsx similarity index 100% rename from submodules/moragents_dockers/frontend/components/Settings/CoinbaseConfig.tsx rename to submodules/moragents_dockers/frontend/components/Credentials/CoinbaseConfig.tsx diff --git a/submodules/moragents_dockers/frontend/components/Credentials/Modal.tsx b/submodules/moragents_dockers/frontend/components/Credentials/Modal.tsx new file mode 100644 index 0000000..df7d775 --- /dev/null +++ b/submodules/moragents_dockers/frontend/components/Credentials/Modal.tsx @@ -0,0 +1,161 @@ +import { useState } from "react"; +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalCloseButton, + Grid, + Box, + Text, + useColorModeValue, + VStack, + Image, + Button, + Flex, +} from "@chakra-ui/react"; +import { TwitterConfig } from "@/components/Credentials/TwitterConfig"; +import { CoinbaseConfig } from "@/components/Credentials/CoinbaseConfig"; +import { OneInchConfig } from "@/components/Credentials/OneInchConfig"; +import { IoArrowBack } from "react-icons/io5"; + +interface ApiOption { + id: string; + name: string; + logo: string; + component: React.FC<{ onSave: () => void }>; +} + +const API_OPTIONS: ApiOption[] = [ + { + id: "twitter", + name: "X/Twitter API", + logo: "/images/x-logo.jpg", + component: TwitterConfig, + }, + { + id: "coinbase", + name: "Coinbase API", + logo: "/images/coinbase-logo.png", + component: CoinbaseConfig, + }, + { + id: "oneinch", + name: "1inch API", + logo: "/images/one-inch-logo.png", + component: OneInchConfig, + }, +]; + +interface ApiCredentialsModalProps { + isOpen: boolean; + onClose: () => void; +} + +export const ApiCredentialsModal: React.FC = ({ + isOpen, + onClose, +}) => { + const [selectedApi, setSelectedApi] = useState(null); + const bgColor = useColorModeValue("white", "gray.800"); + const cardBgColor = useColorModeValue("gray.50", "gray.700"); + const cardHoverBgColor = useColorModeValue("gray.100", "gray.600"); + + const SelectedApiComponent = API_OPTIONS.find( + (api) => api.id === selectedApi + )?.component; + + return ( + { + setSelectedApi(null); + onClose(); + }} + size="xl" + scrollBehavior="inside" + isCentered + > + + + + {selectedApi && ( + + )} + + {selectedApi + ? API_OPTIONS.find((api) => api.id === selectedApi)?.name + : "API Credentials"} + + + + + {!selectedApi ? ( + + + + Configure Your API Integrations + + + Set up your API credentials for various services to enable + advanced features and integrations. + + + + + {API_OPTIONS.map((api) => ( + setSelectedApi(api.id)} + _hover={{ bg: cardHoverBgColor }} + transition="background-color 0.2s" + > + + {`${api.name} + {api.name} + + + ))} + + + ) : ( + SelectedApiComponent && ( + { + setSelectedApi(null); + onClose(); + }} + /> + ) + )} + + + + ); +}; diff --git a/submodules/moragents_dockers/frontend/components/Settings/OneInchConfig.tsx b/submodules/moragents_dockers/frontend/components/Credentials/OneInchConfig.tsx similarity index 100% rename from submodules/moragents_dockers/frontend/components/Settings/OneInchConfig.tsx rename to submodules/moragents_dockers/frontend/components/Credentials/OneInchConfig.tsx diff --git a/submodules/moragents_dockers/frontend/components/Settings/TwitterConfig.tsx b/submodules/moragents_dockers/frontend/components/Credentials/TwitterConfig.tsx similarity index 100% rename from submodules/moragents_dockers/frontend/components/Settings/TwitterConfig.tsx rename to submodules/moragents_dockers/frontend/components/Credentials/TwitterConfig.tsx diff --git a/submodules/moragents_dockers/frontend/components/HeaderBar/index.tsx b/submodules/moragents_dockers/frontend/components/HeaderBar/index.tsx index 9d42cd2..7fab141 100644 --- a/submodules/moragents_dockers/frontend/components/HeaderBar/index.tsx +++ b/submodules/moragents_dockers/frontend/components/HeaderBar/index.tsx @@ -2,10 +2,8 @@ import React, { FC, useState } from "react"; import Image from "next/image"; import { Box, HStack, Spacer, Button, ButtonGroup } from "@chakra-ui/react"; import { ConnectButton } from "@rainbow-me/rainbowkit"; -import { CDPWallets } from "./CDPWallets"; +import { CDPWallets } from "@/components/CDPWallets"; import classes from "./index.module.css"; -import { getHttpClient } from "@/services/constants"; -import { useRouter } from "next/router"; export const HeaderBar: FC = () => { const [walletType, setWalletType] = useState<"cdp" | "metamask">("cdp"); diff --git a/submodules/moragents_dockers/frontend/components/LeftSidebar/index.tsx b/submodules/moragents_dockers/frontend/components/LeftSidebar/index.tsx index 478d666..888c0b2 100644 --- a/submodules/moragents_dockers/frontend/components/LeftSidebar/index.tsx +++ b/submodules/moragents_dockers/frontend/components/LeftSidebar/index.tsx @@ -16,6 +16,7 @@ import { SettingsButton } from "@/components/Settings"; import { Workflows } from "@/components/Workflows"; import styles from "./index.module.css"; import { useRouter } from "next/router"; +import { ApiCredentialsButton } from "@/components/Credentials/Button"; export type LeftSidebarProps = { /** Whether the sidebar is currently open (expanded) or collapsed */ @@ -266,6 +267,7 @@ export const LeftSidebar: FC = ({
+ diff --git a/submodules/moragents_dockers/frontend/components/MessageItem/index.tsx b/submodules/moragents_dockers/frontend/components/MessageItem/index.tsx index 5143775..9f9cb38 100644 --- a/submodules/moragents_dockers/frontend/components/MessageItem/index.tsx +++ b/submodules/moragents_dockers/frontend/components/MessageItem/index.tsx @@ -12,29 +12,17 @@ import { } from "@/services/types"; import { getHumanReadableAgentName } from "@/services/utils"; import { Avatar } from "@/components/Avatar"; -import { SwapMessage } from "@/components/SwapMessage"; -import { ClaimMessage } from "@/components/ClaimMessage/ClaimMessage"; -import { Tweet } from "@/components/Tweet"; +import { SwapMessage } from "@/components/Agents/Swaps/SwapMessage"; +import { ClaimMessage } from "@/components/Agents/MorClaims/ClaimMessage"; +import { Tweet } from "@/components/Agents/Tweet/TweetMessage"; import styles from "./index.module.css"; type MessageItemProps = { message: ChatMessage; - onCancelSwap: (fromAction: number) => void; - onSwapSubmit: (swapTx: any) => void; - onClaimSubmit: (claimTx: any) => void; - isLastSwapMessage: boolean; - isLastClaimMessage: boolean; }; -export const MessageItem: FC = ({ - message, - onCancelSwap, - onSwapSubmit, - onClaimSubmit, - isLastSwapMessage, - isLastClaimMessage, -}) => { +export const MessageItem: FC = ({ message }) => { const isUser = message.role === "user"; const { content, error_message } = message; diff --git a/submodules/moragents_dockers/frontend/components/MessageList/index.tsx b/submodules/moragents_dockers/frontend/components/MessageList/index.tsx index 3b07e26..6119d19 100644 --- a/submodules/moragents_dockers/frontend/components/MessageList/index.tsx +++ b/submodules/moragents_dockers/frontend/components/MessageList/index.tsx @@ -3,19 +3,7 @@ import { Box } from "@chakra-ui/react"; import { ChatMessage } from "@/services/types"; import { MessageItem } from "../MessageItem"; -type MessageListProps = { - messages: ChatMessage[]; - onCancelSwap: (fromAction: number) => void; - onSwapSubmit: (swapTx: any) => void; - onClaimSubmit: (claimTx: any) => void; -}; - -export const MessageList: FC = ({ - messages, - onCancelSwap, - onSwapSubmit, - onClaimSubmit, -}) => { +export const MessageList: FC<{ messages: ChatMessage[] }> = ({ messages }) => { return ( = ({ }} > {messages.map((message, index) => ( - + ))} ); diff --git a/submodules/moragents_dockers/frontend/components/Settings/GeneralSettings.tsx b/submodules/moragents_dockers/frontend/components/Settings/GeneralSettings.tsx new file mode 100644 index 0000000..5e29c59 --- /dev/null +++ b/submodules/moragents_dockers/frontend/components/Settings/GeneralSettings.tsx @@ -0,0 +1,57 @@ +import { useState } from "react"; +import { + VStack, + FormControl, + FormLabel, + Textarea, + Button, +} from "@chakra-ui/react"; + +interface GeneralSettingsProps { + onSave: () => void; +} + +export const GeneralSettings: React.FC = ({ onSave }) => { + const [settings, setSettings] = useState({ + aiPersonality: "", + bio: "", + }); + + const handleSave = () => { + // TODO: Save to backend and incorporate into agent + console.log("Saving general settings:", settings); + onSave(); + }; + + return ( + + + Give your AI a personality +