From f728383a50e2f94a0829360f867eb1d69fbabcf3 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 25 Sep 2024 18:15:48 +0700 Subject: [PATCH 01/28] Add first UI --- src/assets/svg/ic_send.svg | 4 + src/assets/svg/kai_avatar.svg | 7 + src/assets/svg/kai_avatar2.svg | 7 + src/components/Kai/KaiContent.tsx | 106 +++++++++++++++ src/components/Kai/KaiStyledComponents.tsx | 147 +++++++++++++++++++++ src/components/Kai/actions.ts | 49 +++++++ src/components/Kai/index.tsx | 93 +++++++++++++ src/pages/App.tsx | 2 + 8 files changed, 415 insertions(+) create mode 100644 src/assets/svg/ic_send.svg create mode 100644 src/assets/svg/kai_avatar.svg create mode 100644 src/assets/svg/kai_avatar2.svg create mode 100644 src/components/Kai/KaiContent.tsx create mode 100644 src/components/Kai/KaiStyledComponents.tsx create mode 100644 src/components/Kai/actions.ts create mode 100644 src/components/Kai/index.tsx diff --git a/src/assets/svg/ic_send.svg b/src/assets/svg/ic_send.svg new file mode 100644 index 0000000000..5221354aeb --- /dev/null +++ b/src/assets/svg/ic_send.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/kai_avatar.svg b/src/assets/svg/kai_avatar.svg new file mode 100644 index 0000000000..76b7104300 --- /dev/null +++ b/src/assets/svg/kai_avatar.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/svg/kai_avatar2.svg b/src/assets/svg/kai_avatar2.svg new file mode 100644 index 0000000000..d47a1a1d74 --- /dev/null +++ b/src/assets/svg/kai_avatar2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/Kai/KaiContent.tsx b/src/components/Kai/KaiContent.tsx new file mode 100644 index 0000000000..45301a1e67 --- /dev/null +++ b/src/components/Kai/KaiContent.tsx @@ -0,0 +1,106 @@ +import { ChangeEvent, KeyboardEvent, useState } from 'react' + +import { ReactComponent as KaiAvatar } from 'assets/svg/kai_avatar2.svg' + +import { + ActionButton, + ActionPanel, + ChatInput, + ChatWrapper, + Divider, + KaiHeaderWrapper, + Loader, + LoadingWrapper, + SendIcon, + SubTextSpan, + WelcomeText, +} from './KaiStyledComponents' +import { KaiAction, MAIN_MENU } from './actions' + +const DEFAULT_LOADING_TEXT = 'KAI is checking the data ...' +const DEFAULT_CHAT_PLACEHOLDER_TEXT = 'Ask me anything or select' + +const KaiContent = () => { + const [chatPlaceHolderText, _setChatPlaceHolderText] = useState(DEFAULT_CHAT_PLACEHOLDER_TEXT) + const [loading, _setLoading] = useState(false) + const [loadingText, _setLoadingText] = useState(DEFAULT_LOADING_TEXT) + + const onSubmitChat = (text: string) => { + console.log('Submitted chat: ' + text) + } + + return ( + <> + + GM! What can I do for you today? πŸ‘‹ + + {MAIN_MENU.map((action: KaiAction, index: number) => ( + + {action.title} + + ))} + + {loading && } + + + ) +} + +const KaiHeader = () => { + return ( + <> + + + I'm KAI + Kyber Assistant Interface + + + + ) +} + +const KaiLoading = ({ loadingText }: { loadingText: string }) => { + return ( + + + {loadingText} + + ) +} + +const KaiChat = ({ + chatPlaceHolderText, + onSubmitChat, + disabled = false, +}: { + chatPlaceHolderText: string + onSubmitChat: (text: string) => void + disabled?: boolean +}) => { + const [chatInput, setChatInput] = useState('') + + const onChangeChatInput = (e: ChangeEvent) => setChatInput(e.target.value) + + const handleEnter = (e: KeyboardEvent) => { + if (e.key !== 'Enter') return + onSubmitChat(chatInput) + } + + return ( + + + onSubmitChat(chatInput)} /> + + ) +} + +export default KaiContent diff --git a/src/components/Kai/KaiStyledComponents.tsx b/src/components/Kai/KaiStyledComponents.tsx new file mode 100644 index 0000000000..ce9c5969c3 --- /dev/null +++ b/src/components/Kai/KaiStyledComponents.tsx @@ -0,0 +1,147 @@ +import { rgba } from 'polished' +import styled, { css, keyframes } from 'styled-components' + +import { ReactComponent as Send } from 'assets/svg/ic_send.svg' + +import { Space } from './actions' + +export const KaiHeaderWrapper = styled.div` + display: flex; + flex-wrap: wrap; + gap: 6px; + align-items: center; +` + +export const SubTextSpan = styled.span` + color: ${({ theme }) => theme.subText}; +` + +export const Divider = styled.div` + background-color: #505050; + height: 1px; + width: 100%; + margin: 10px 0 14px; +` + +export const WelcomeText = styled.div` + margin-bottom: 16px; +` + +export const ChatWrapper = styled.div<{ disabled: boolean }>` + position: relative; + height: 36px; + margin-top: 16px; + + ${({ disabled }) => + disabled && + css` + opacity: 0.4; + `} +` + +export const ChatInput = styled.input` + position: absolute; + display: flex; + padding: 10px 30px 13px 16px; + align-items: center; + width: 100%; + white-space: nowrap; + background: none; + border: none; + outline: none; + border-radius: 8px; + color: ${({ theme }) => theme.text}; + border-style: solid; + border: 1px solid ${({ theme }) => theme.buttonBlack}; + background: ${({ theme }) => theme.buttonBlack}; + transition: border 100ms; + appearance: none; + -webkit-appearance: none; + + ::placeholder { + color: ${({ theme }) => theme.border}; + font-size: 13.5px; + ${({ theme }) => theme.mediaWidth.upToSmall` + font-size: 12.5px; + `}; + } + + :focus { + border: 1px solid ${({ theme }) => theme.primary}; + outline: none; + } +` + +export const SendIcon = styled(Send)` + position: absolute; + right: 12px; + top: 12px; + color: transparent; + transition: 0.1s ease-in-out; + cursor: pointer; + + :hover { + color: ${({ theme }) => theme.primary}; + border-color: transparent; + } +` + +export const LoadingWrapper = styled.div` + color: ${({ theme }) => theme.subText}; + display: flex; + align-items: center; + gap: 6px; +` + +const loadingKeyFrame = keyframes` + 100%{transform: rotate(.5turn)} +` + +export const Loader = styled.div` + width: 16px; + aspect-ratio: 1; + --c: ${({ theme }) => `no-repeat radial-gradient(farthest-side, ${theme.subText} 92%, #0000)`}; + background: var(--c) 50% 0, var(--c) 50% 100%, var(--c) 100% 50%, var(--c) 0 50%; + background-size: 3px 3px; + animation: ${loadingKeyFrame} 1s infinite; + position: relative; + + ::before { + content: ''; + position: absolute; + inset: 0; + margin: 1px; + background: ${({ theme }) => `repeating-conic-gradient(#0000 0 35deg, ${theme.subText} 0 90deg)`}; + mask: radial-gradient(farthest-side, #0000 calc(100% - 1px), #000 0); + -webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - 1px), #000 0); + border-radius: 50%; + } +` + +export const ActionPanel = styled.div` + display: flex; + flex-wrap: wrap; + gap: 12px; + width: 100%; +` + +export const ActionButton = styled.div<{ width: number }>` + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + color: #fafafa; + height: 36px; + background-color: ${({ theme }) => rgba(theme.white, 0.04)}; + transition: 0.1s ease-in-out; + cursor: pointer; + + :hover { + background-color: ${({ theme }) => rgba(theme.white, 0.08)}; + } + + ${({ width }) => + css` + width: ${width === Space.FULL_WIDTH ? width + '%' : `calc(${width}% - 6px)`}; + `} +` diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts new file mode 100644 index 0000000000..f10c3a9de8 --- /dev/null +++ b/src/components/Kai/actions.ts @@ -0,0 +1,49 @@ +export enum Space { + HALF_WIDTH = 50, + FULL_WIDTH = 100, +} + +export interface KaiAction { + title: string + space: Space +} + +interface ListActions { + [actionKey: string]: KaiAction +} + +export const LIST_ACTIONS: ListActions = { + CHECK_TOKEN_PRICE: { + title: 'Check the token price', + space: Space.FULL_WIDTH, + }, + SEE_MARKET_TRENDS: { + title: 'See market trends', + space: Space.FULL_WIDTH, + }, + FIND_HIGH_APY_POOLS: { + title: 'Find high APY pools', + space: Space.FULL_WIDTH, + }, + BUY_TOKENS: { + title: 'Buy tokens', + space: Space.HALF_WIDTH, + }, + SELL_TOKENS: { + title: 'Sell tokens', + space: Space.HALF_WIDTH, + }, + ADD_LIQUIDITY: { + title: 'Add liquidity', + space: Space.FULL_WIDTH, + }, +} + +export const MAIN_MENU: KaiAction[] = [ + LIST_ACTIONS.CHECK_TOKEN_PRICE, + LIST_ACTIONS.SEE_MARKET_TRENDS, + LIST_ACTIONS.FIND_HIGH_APY_POOLS, + LIST_ACTIONS.BUY_TOKENS, + LIST_ACTIONS.SELL_TOKENS, + LIST_ACTIONS.ADD_LIQUIDITY, +] diff --git a/src/components/Kai/index.tsx b/src/components/Kai/index.tsx new file mode 100644 index 0000000000..329d559e93 --- /dev/null +++ b/src/components/Kai/index.tsx @@ -0,0 +1,93 @@ +import { motion } from 'framer-motion' +import { useState } from 'react' +import styled from 'styled-components' + +import { ReactComponent as KaiAvatarSvg } from 'assets/svg/kai_avatar.svg' + +import KaiContent from './KaiContent' + +const Wrapper = styled(motion.div)` + position: fixed; + bottom: 1rem; + right: 8rem; + z-index: 1; + height: 36px; + + ${({ theme }) => theme.mediaWidth.upToLarge` + bottom: 120px; + right: 1rem; + `}; +` + +const KaiAvatar = styled(KaiAvatarSvg)` + cursor: pointer; +` + +const Modal = styled(motion.div)` + position: fixed; + bottom: 5.2rem; + right: 1rem; + z-index: 1; + font-size: 14px; + width: fit-content; + height: fit-content; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + + ${({ theme }) => theme.mediaWidth.upToLarge` + bottom: 174px; + `} +` + +const ModalContent = styled.div` + background: ${({ theme }) => theme.tableHeader}; + padding: 20px 24px 26px; + border-radius: 12px; + width: 320px; + + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + width: calc(100vw - 2rem); + `} +` + +const kaiAnimate = { + enter: { + opacity: 1, + rotateX: 0, + transition: { + duration: 0.3, + }, + display: 'block', + }, + exit: { + opacity: 0, + rotateX: -15, + transition: { + duration: 0.3, + delay: 0.2, + }, + transitionEnd: { + display: 'none', + }, + }, +} + +const Kai = () => { + const [openKai, setOpenKai] = useState(false) + + const onOpenKai = () => setOpenKai(!openKai) + + return ( + <> + + + + + + + + + + ) +} + +export default Kai diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 5a1c47884a..fdf3fc8bbc 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -12,6 +12,7 @@ import AppHaveUpdate from 'components/AppHaveUpdate' import ErrorBoundary from 'components/ErrorBoundary' import Footer from 'components/Footer/Footer' import Header from 'components/Header' +import Kai from 'components/Kai' import Loader from 'components/LocalLoader' import ModalsGlobal from 'components/ModalsGlobal' import ProtectedRoute from 'components/ProtectedRoute' @@ -218,6 +219,7 @@ export default function App() { {!isPartnerSwap && } +
From ef7ce94bcc5610f1a0a1115ecc45e3470c1a439f Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 25 Sep 2024 23:50:56 +0700 Subject: [PATCH 02/28] Complete Assistant full flow --- src/components/Kai/KaiContent.tsx | 106 ---------- src/components/Kai/KaiPanel.tsx | 191 ++++++++++++++++++ src/components/Kai/actions.ts | 73 ++++++- src/components/Kai/index.tsx | 52 +---- .../{KaiStyledComponents.tsx => styled.tsx} | 95 ++++++++- 5 files changed, 343 insertions(+), 174 deletions(-) delete mode 100644 src/components/Kai/KaiContent.tsx create mode 100644 src/components/Kai/KaiPanel.tsx rename src/components/Kai/{KaiStyledComponents.tsx => styled.tsx} (59%) diff --git a/src/components/Kai/KaiContent.tsx b/src/components/Kai/KaiContent.tsx deleted file mode 100644 index 45301a1e67..0000000000 --- a/src/components/Kai/KaiContent.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { ChangeEvent, KeyboardEvent, useState } from 'react' - -import { ReactComponent as KaiAvatar } from 'assets/svg/kai_avatar2.svg' - -import { - ActionButton, - ActionPanel, - ChatInput, - ChatWrapper, - Divider, - KaiHeaderWrapper, - Loader, - LoadingWrapper, - SendIcon, - SubTextSpan, - WelcomeText, -} from './KaiStyledComponents' -import { KaiAction, MAIN_MENU } from './actions' - -const DEFAULT_LOADING_TEXT = 'KAI is checking the data ...' -const DEFAULT_CHAT_PLACEHOLDER_TEXT = 'Ask me anything or select' - -const KaiContent = () => { - const [chatPlaceHolderText, _setChatPlaceHolderText] = useState(DEFAULT_CHAT_PLACEHOLDER_TEXT) - const [loading, _setLoading] = useState(false) - const [loadingText, _setLoadingText] = useState(DEFAULT_LOADING_TEXT) - - const onSubmitChat = (text: string) => { - console.log('Submitted chat: ' + text) - } - - return ( - <> - - GM! What can I do for you today? πŸ‘‹ - - {MAIN_MENU.map((action: KaiAction, index: number) => ( - - {action.title} - - ))} - - {loading && } - - - ) -} - -const KaiHeader = () => { - return ( - <> - - - I'm KAI - Kyber Assistant Interface - - - - ) -} - -const KaiLoading = ({ loadingText }: { loadingText: string }) => { - return ( - - - {loadingText} - - ) -} - -const KaiChat = ({ - chatPlaceHolderText, - onSubmitChat, - disabled = false, -}: { - chatPlaceHolderText: string - onSubmitChat: (text: string) => void - disabled?: boolean -}) => { - const [chatInput, setChatInput] = useState('') - - const onChangeChatInput = (e: ChangeEvent) => setChatInput(e.target.value) - - const handleEnter = (e: KeyboardEvent) => { - if (e.key !== 'Enter') return - onSubmitChat(chatInput) - } - - return ( - - - onSubmitChat(chatInput)} /> - - ) -} - -export default KaiContent diff --git a/src/components/Kai/KaiPanel.tsx b/src/components/Kai/KaiPanel.tsx new file mode 100644 index 0000000000..b0f5b2363e --- /dev/null +++ b/src/components/Kai/KaiPanel.tsx @@ -0,0 +1,191 @@ +import { ChangeEvent, KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react' + +import { ReactComponent as KaiAvatar } from 'assets/svg/kai_avatar2.svg' + +import { ActionType, KAI_ACTIONS, KaiAction, KaiOption } from './actions' +import { + ActionButton, + ActionPanel, + ActionText, + ChatInput, + ChatPanel, + ChatWrapper, + Divider, + KaiHeaderWrapper, + Loader, + LoadingWrapper, + SendIcon, + SubTextSpan, + UserMessage, + UserMessageWrapper, +} from './styled' + +const DEFAULT_LOADING_TEXT = 'KAI is checking the data ...' +const DEFAULT_CHAT_PLACEHOLDER_TEXT = 'Write a message...' + +const KaiPanel = () => { + const chatPanelRef = useRef(null) + + const [chatPlaceHolderText, setChatPlaceHolderText] = useState(DEFAULT_CHAT_PLACEHOLDER_TEXT) + const [loading, setLoading] = useState(false) + const [loadingText, setLoadingText] = useState(DEFAULT_LOADING_TEXT) + // const [listActions, setListActions] = useState([KAI_ACTIONS.WELCOME]) + const [listActions, setListActions] = useState([ + KAI_ACTIONS.WELCOME, + KAI_ACTIONS.COMING_SOON, + { + title: 'Add liquidity', + type: ActionType.USER_MESSAGE, + }, + { + title: 'Add liquidity', + type: ActionType.USER_MESSAGE, + }, + { + title: 'Add liquidity', + type: ActionType.USER_MESSAGE, + }, + KAI_ACTIONS.WELCOME, + ]) + + const lastAction = useMemo(() => { + const cloneListActions = [...listActions] + cloneListActions.reverse() + + return cloneListActions.find((action: KaiAction) => action.type !== ActionType.INVALID) + }, [listActions]) + + const onSubmitChat = (text: string) => { + if (lastAction?.loadingText) setLoadingText(lastAction.loadingText) + setLoading(true) + onChangeListActions({ + title: text, + type: ActionType.USER_MESSAGE, + }) + setLoading(false) + setLoadingText(DEFAULT_LOADING_TEXT) + } + + const onChangeListActions = (newAction: KaiAction) => { + const cloneListActions = [...listActions] + cloneListActions.push(newAction) + setListActions(cloneListActions) + } + + useEffect(() => { + if (lastAction?.placeholder) setChatPlaceHolderText(lastAction.placeholder) + else setChatPlaceHolderText(DEFAULT_CHAT_PLACEHOLDER_TEXT) + }, [lastAction]) + + useEffect(() => { + if (chatPanelRef.current) + chatPanelRef.current.scrollTo({ top: chatPanelRef.current.scrollHeight, behavior: 'smooth' }) + }, [listActions]) + + return ( + <> + + + +
GM! What can I do for you today? πŸ‘‹
+ {listActions.map((action: KaiAction, index: number) => { + if (action.type === ActionType.OPTION) + return ( + + {action.data?.map((option: KaiOption, optionIndex: number) => ( + onSubmitChat(option.title)}> + {option.title} + + ))} + + ) + else if (action.type === ActionType.TEXT || action.type === ActionType.INVALID) + return {action.title} + else if (action.type === ActionType.USER_MESSAGE) + return ( + + + {action.title} + + + ) + + return null + })} +
+ + {loading && } + + + ) +} + +const KaiHeader = () => { + return ( + <> + + + I'm KAI + Kyber Assistant Interface + + + + ) +} + +const KaiLoading = ({ loadingText }: { loadingText: string }) => { + return ( + + + {loadingText} + + ) +} + +const KaiChat = ({ + chatPlaceHolderText, + onSubmitChat, + disabled = false, +}: { + chatPlaceHolderText: string + onSubmitChat: (text: string) => void + disabled?: boolean +}) => { + const [chatInput, setChatInput] = useState('') + + const onChangeChatInput = (e: ChangeEvent) => setChatInput(e.target.value) + + const handleEnter = (e: KeyboardEvent) => { + if (e.key !== 'Enter') return + onSubmitChat(chatInput) + setChatInput('') + } + + return ( + + + { + onSubmitChat(chatInput) + setChatInput('') + }} + /> + + ) +} + +export default KaiPanel diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index f10c3a9de8..ca1554e7f7 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -1,18 +1,38 @@ export enum Space { - HALF_WIDTH = 50, - FULL_WIDTH = 100, + HALF_WIDTH = 'calc(50% - 6px)', + FULL_WIDTH = '100%', +} + +export enum ActionType { + TEXT, + OPTION, + USER_MESSAGE, + INVALID, } export interface KaiAction { + title?: string + type: ActionType + data?: KaiOption[] + placeholder?: string + loadingText?: string + response?: (answer: string) => KaiAction[] +} + +export interface KaiOption { title: string space: Space } +interface ListOptions { + [optionKey: string]: KaiOption +} + interface ListActions { [actionKey: string]: KaiAction } -export const LIST_ACTIONS: ListActions = { +const KAI_OPTIONS: ListOptions = { CHECK_TOKEN_PRICE: { title: 'Check the token price', space: Space.FULL_WIDTH, @@ -37,13 +57,46 @@ export const LIST_ACTIONS: ListActions = { title: 'Add liquidity', space: Space.FULL_WIDTH, }, + BACK_TO_MENU: { + title: 'Back to the main menu', + space: Space.FULL_WIDTH, + }, } -export const MAIN_MENU: KaiAction[] = [ - LIST_ACTIONS.CHECK_TOKEN_PRICE, - LIST_ACTIONS.SEE_MARKET_TRENDS, - LIST_ACTIONS.FIND_HIGH_APY_POOLS, - LIST_ACTIONS.BUY_TOKENS, - LIST_ACTIONS.SELL_TOKENS, - LIST_ACTIONS.ADD_LIQUIDITY, +export const MAIN_MENU: KaiOption[] = [ + KAI_OPTIONS.CHECK_TOKEN_PRICE, + KAI_OPTIONS.SEE_MARKET_TRENDS, + KAI_OPTIONS.FIND_HIGH_APY_POOLS, + KAI_OPTIONS.BUY_TOKENS, + KAI_OPTIONS.SELL_TOKENS, + KAI_OPTIONS.ADD_LIQUIDITY, ] + +export const KAI_ACTIONS: ListActions = { + WELCOME: { + type: ActionType.OPTION, + data: MAIN_MENU, + placeholder: 'Ask me anything or select...', + response: (answer: string) => { + if (MAIN_MENU.find((option: KaiOption) => answer.trim().toLowerCase() === option.title.toLowerCase())) + return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] + return [KAI_ACTIONS.INVALID] + }, + }, + BACK_TO_MENU: { + type: ActionType.OPTION, + data: [KAI_OPTIONS.BACK_TO_MENU], + response: (answer: string) => { + if (answer === KAI_OPTIONS.BACK_TO_MENU.title) return [KAI_ACTIONS.WELCOME] + return [KAI_ACTIONS.INVALID] + }, + }, + COMING_SOON: { + title: 'Coming soon, do you want to go back to the main menu?', + type: ActionType.TEXT, + }, + INVALID: { + title: 'Invalid input, please follow the instruction!', + type: ActionType.INVALID, + }, +} diff --git a/src/components/Kai/index.tsx b/src/components/Kai/index.tsx index 329d559e93..8a9a70dd2f 100644 --- a/src/components/Kai/index.tsx +++ b/src/components/Kai/index.tsx @@ -1,53 +1,7 @@ -import { motion } from 'framer-motion' import { useState } from 'react' -import styled from 'styled-components' -import { ReactComponent as KaiAvatarSvg } from 'assets/svg/kai_avatar.svg' - -import KaiContent from './KaiContent' - -const Wrapper = styled(motion.div)` - position: fixed; - bottom: 1rem; - right: 8rem; - z-index: 1; - height: 36px; - - ${({ theme }) => theme.mediaWidth.upToLarge` - bottom: 120px; - right: 1rem; - `}; -` - -const KaiAvatar = styled(KaiAvatarSvg)` - cursor: pointer; -` - -const Modal = styled(motion.div)` - position: fixed; - bottom: 5.2rem; - right: 1rem; - z-index: 1; - font-size: 14px; - width: fit-content; - height: fit-content; - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); - - ${({ theme }) => theme.mediaWidth.upToLarge` - bottom: 174px; - `} -` - -const ModalContent = styled.div` - background: ${({ theme }) => theme.tableHeader}; - padding: 20px 24px 26px; - border-radius: 12px; - width: 320px; - - ${({ theme }) => theme.mediaWidth.upToExtraSmall` - width: calc(100vw - 2rem); - `} -` +import KaiPanel from './KaiPanel' +import { KaiAvatar, Modal, ModalContent, Wrapper } from './styled' const kaiAnimate = { enter: { @@ -83,7 +37,7 @@ const Kai = () => { - + diff --git a/src/components/Kai/KaiStyledComponents.tsx b/src/components/Kai/styled.tsx similarity index 59% rename from src/components/Kai/KaiStyledComponents.tsx rename to src/components/Kai/styled.tsx index ce9c5969c3..e827ccc118 100644 --- a/src/components/Kai/KaiStyledComponents.tsx +++ b/src/components/Kai/styled.tsx @@ -1,9 +1,52 @@ +import { motion } from 'framer-motion' import { rgba } from 'polished' import styled, { css, keyframes } from 'styled-components' import { ReactComponent as Send } from 'assets/svg/ic_send.svg' +import { ReactComponent as KaiAvatarSvg } from 'assets/svg/kai_avatar.svg' -import { Space } from './actions' +export const Wrapper = styled(motion.div)` + position: fixed; + bottom: 1rem; + right: 8rem; + z-index: 1; + height: 36px; + + ${({ theme }) => theme.mediaWidth.upToLarge` + bottom: 120px; + right: 1rem; + `}; +` + +export const KaiAvatar = styled(KaiAvatarSvg)` + cursor: pointer; +` + +export const Modal = styled(motion.div)` + position: fixed; + bottom: 5.2rem; + right: 1rem; + z-index: 1; + font-size: 14px; + width: fit-content; + height: fit-content; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + + ${({ theme }) => theme.mediaWidth.upToLarge` + bottom: 174px; + `} +` + +export const ModalContent = styled.div` + background: ${({ theme }) => theme.tableHeader}; + padding: 20px 24px 26px; + border-radius: 12px; + width: 320px; + + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + width: calc(100vw - 2rem); + `} +` export const KaiHeaderWrapper = styled.div` display: flex; @@ -23,10 +66,6 @@ export const Divider = styled.div` margin: 10px 0 14px; ` -export const WelcomeText = styled.div` - margin-bottom: 16px; -` - export const ChatWrapper = styled.div<{ disabled: boolean }>` position: relative; height: 36px; @@ -52,8 +91,8 @@ export const ChatInput = styled.input` border-radius: 8px; color: ${({ theme }) => theme.text}; border-style: solid; - border: 1px solid ${({ theme }) => theme.buttonBlack}; - background: ${({ theme }) => theme.buttonBlack}; + border: 1px solid ${({ theme }) => theme.background}; + background: ${({ theme }) => theme.background}; transition: border 100ms; appearance: none; -webkit-appearance: none; @@ -91,6 +130,7 @@ export const LoadingWrapper = styled.div` display: flex; align-items: center; gap: 6px; + margin-top: 12px; ` const loadingKeyFrame = keyframes` @@ -118,14 +158,20 @@ export const Loader = styled.div` } ` +export const ChatPanel = styled.div` + max-height: 400px; + overflow: scroll; +` + export const ActionPanel = styled.div` display: flex; flex-wrap: wrap; gap: 12px; width: 100%; + margin-top: 16px; ` -export const ActionButton = styled.div<{ width: number }>` +export const ActionButton = styled.div<{ width: string }>` display: flex; align-items: center; justify-content: center; @@ -142,6 +188,37 @@ export const ActionButton = styled.div<{ width: number }>` ${({ width }) => css` - width: ${width === Space.FULL_WIDTH ? width + '%' : `calc(${width}% - 6px)`}; + width: ${width}; + `} +` + +export const ActionText = styled.div` + margin-top: 16px; +` + +export const UserMessageWrapper = styled.div<{ havePrevious: boolean }>` + margin-top: 16px; + width: 100%; + display: flex; + justify-content: flex-end; + + ${({ havePrevious }) => + havePrevious && + css` + margin-top: 4px; + `} +` + +export const UserMessage = styled.p<{ havePrevious: boolean; haveFollowing: boolean }>` + margin: 0; + background-color: ${({ theme }) => theme.darkerGreen}; + width: fit-content; + border-radius: 16px; + padding: 8px 12px; + + ${({ havePrevious, haveFollowing }) => + css` + border-top-right-radius: ${havePrevious ? 4 : 16}px; + border-bottom-right-radius: ${haveFollowing ? 4 : 16}px; `} ` From bd9772f5750329831e8c4856244c91944d07d97b Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 02:22:16 +0700 Subject: [PATCH 03/28] Add flow token check price --- src/components/Kai/KaiPanel.tsx | 145 +++++++++++++++++--------------- src/components/Kai/actions.ts | 119 +++++++++++++++++++++++++- src/components/Kai/styled.tsx | 12 ++- 3 files changed, 205 insertions(+), 71 deletions(-) diff --git a/src/components/Kai/KaiPanel.tsx b/src/components/Kai/KaiPanel.tsx index b0f5b2363e..84a5834eca 100644 --- a/src/components/Kai/KaiPanel.tsx +++ b/src/components/Kai/KaiPanel.tsx @@ -29,47 +29,59 @@ const KaiPanel = () => { const [chatPlaceHolderText, setChatPlaceHolderText] = useState(DEFAULT_CHAT_PLACEHOLDER_TEXT) const [loading, setLoading] = useState(false) const [loadingText, setLoadingText] = useState(DEFAULT_LOADING_TEXT) - // const [listActions, setListActions] = useState([KAI_ACTIONS.WELCOME]) - const [listActions, setListActions] = useState([ - KAI_ACTIONS.WELCOME, - KAI_ACTIONS.COMING_SOON, - { - title: 'Add liquidity', - type: ActionType.USER_MESSAGE, - }, - { - title: 'Add liquidity', - type: ActionType.USER_MESSAGE, - }, - { - title: 'Add liquidity', - type: ActionType.USER_MESSAGE, - }, - KAI_ACTIONS.WELCOME, - ]) + const [listActions, setListActions] = useState([KAI_ACTIONS.MAIN_MENU]) + // const [listActions, setListActions] = useState([ + // KAI_ACTIONS.MAIN_MENU, + // KAI_ACTIONS.COMING_SOON, + // { + // title: 'Add liquidity', + // type: ActionType.USER_MESSAGE, + // }, + // { + // title: 'Add liquidity', + // type: ActionType.USER_MESSAGE, + // }, + // { + // title: 'Add liquidity', + // type: ActionType.USER_MESSAGE, + // }, + // KAI_ACTIONS.MAIN_MENU, + // ]) const lastAction = useMemo(() => { const cloneListActions = [...listActions] cloneListActions.reverse() - return cloneListActions.find((action: KaiAction) => action.type !== ActionType.INVALID) + return cloneListActions.find( + (action: KaiAction) => action.type !== ActionType.INVALID && action.type !== ActionType.USER_MESSAGE, + ) }, [listActions]) const onSubmitChat = (text: string) => { - if (lastAction?.loadingText) setLoadingText(lastAction.loadingText) + if (loading || !lastAction) return + if (lastAction.loadingText) setLoadingText(lastAction.loadingText) setLoading(true) - onChangeListActions({ - title: text, - type: ActionType.USER_MESSAGE, - }) - setLoading(false) - setLoadingText(DEFAULT_LOADING_TEXT) + onChangeListActions([ + { + title: text, + type: ActionType.USER_MESSAGE, + }, + ]) } - const onChangeListActions = (newAction: KaiAction) => { + const onChangeListActions = (newActions: KaiAction[]) => { const cloneListActions = [...listActions] - cloneListActions.push(newAction) - setListActions(cloneListActions) + setListActions(cloneListActions.concat(newActions)) + } + + const getActionResponse = async () => { + const lastUserAction = listActions[listActions.length - 1] + if (lastUserAction?.type === ActionType.USER_MESSAGE) { + const newActions: KaiAction[] = (await lastAction?.response?.(lastUserAction?.title?.toLowerCase() || '')) || [] + if (newActions.length) onChangeListActions(newActions) + setLoading(false) + setLoadingText(DEFAULT_LOADING_TEXT) + } } useEffect(() => { @@ -82,41 +94,41 @@ const KaiPanel = () => { chatPanelRef.current.scrollTo({ top: chatPanelRef.current.scrollHeight, behavior: 'smooth' }) }, [listActions]) + useEffect(() => { + getActionResponse() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [listActions]) + return ( <>
GM! What can I do for you today? πŸ‘‹
- {listActions.map((action: KaiAction, index: number) => { - if (action.type === ActionType.OPTION) - return ( - - {action.data?.map((option: KaiOption, optionIndex: number) => ( - onSubmitChat(option.title)}> - {option.title} - - ))} - - ) - else if (action.type === ActionType.TEXT || action.type === ActionType.INVALID) - return {action.title} - else if (action.type === ActionType.USER_MESSAGE) - return ( - - - {action.title} - - - ) - - return null - })} + {listActions.map((action: KaiAction, index: number) => + action.type === ActionType.OPTION ? ( + + {action.data?.map((option: KaiOption, optionIndex: number) => ( + onSubmitChat(option.title)}> + {option.title} + + ))} + + ) : action.type === ActionType.TEXT || action.type === ActionType.INVALID ? ( + {action.title} + ) : action.type === ActionType.USER_MESSAGE ? ( + + + {action.title} + + + ) : null, + )}
{loading && } @@ -160,10 +172,15 @@ const KaiChat = ({ const onChangeChatInput = (e: ChangeEvent) => setChatInput(e.target.value) + const handleSubmitChatInput = () => { + if (disabled || !chatInput) return + onSubmitChat(chatInput.trim()) + setChatInput('') + } + const handleEnter = (e: KeyboardEvent) => { if (e.key !== 'Enter') return - onSubmitChat(chatInput) - setChatInput('') + handleSubmitChatInput() } return ( @@ -177,13 +194,9 @@ const KaiChat = ({ onChange={onChangeChatInput} onKeyDown={handleEnter} autoComplete="off" + disabled={disabled} /> - { - onSubmitChat(chatInput) - setChatInput('') - }} - /> + ) } diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index ca1554e7f7..1623ef0d1e 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -73,24 +73,131 @@ export const MAIN_MENU: KaiOption[] = [ ] export const KAI_ACTIONS: ListActions = { - WELCOME: { + MAIN_MENU: { type: ActionType.OPTION, data: MAIN_MENU, placeholder: 'Ask me anything or select...', response: (answer: string) => { + if (answer === KAI_OPTIONS.CHECK_TOKEN_PRICE.title.toLowerCase()) return [KAI_ACTIONS.TYPE_TOKEN_TO_CHECK_PRICE] if (MAIN_MENU.find((option: KaiOption) => answer.trim().toLowerCase() === option.title.toLowerCase())) return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] return [KAI_ACTIONS.INVALID] }, }, + TYPE_TOKEN_TO_CHECK_PRICE: { + title: 'Great! Which token are you interested in? Just type the name or address.', + type: ActionType.TEXT, + response: async (answer: string) => { + const filter = { + chainId: 8453, // Base + search: answer, + page: '1', + // pageSize: 80, + pageSize: 5, + chainIds: 8453, // Base + } + + try { + const res = await fetch( + `${import.meta.env.VITE_TOKEN_API_URL}/v1/public/assets?` + new URLSearchParams(filter).toString(), + { + method: 'GET', + }, + ) + const { data } = await res.json() + console.log(data.assets) + + if (data.assets.length === 1) { + const token = data.assets[0] + + return [ + { + title: `Here’s what I’ve got for ${answer}`, + type: ActionType.TEXT, + }, + { + type: ActionType.TEXT, + title: ` + - πŸ“ˆ Price: ${token.tokens[0]?.priceBuy || ''} + - πŸ”„ 24h Price Change: ${-token.tokens[0]?.priceBuyChange24h} + - πŸ’Έ 24h Volume: ${token.volume24h} + - 🏦 Market Cap: ${token.marketCap} + `, + }, + KAI_ACTIONS.WOULD_LIKE_TO_DO_SOMETHING_ELSE, + KAI_ACTIONS.DO_SOMETHING_AFTER_CHECK_PRICE, + ] + } else if (data.assets.length > 1) { + return [ + { + title: `Here are the tokens I found. Which one do you mean?`, + type: ActionType.TEXT, + }, + { + type: ActionType.OPTION, + data: data.assets.map((item: any) => ({ + title: item.name, + space: Space.HALF_WIDTH, + })), + response: (innerAnswer: string) => { + const token = data.assets.find((item: any) => item.name.toLowerCase() === innerAnswer) + if (token) + return [ + { + title: `Here’s what I’ve got for ${innerAnswer}`, + type: ActionType.TEXT, + }, + { + type: ActionType.TEXT, + title: ` + - πŸ“ˆ Price: ${token.tokens[0]?.priceBuy || ''} + - πŸ”„ 24h Price Change: ${-token.tokens[0]?.priceBuyChange24h} + - πŸ’Έ 24h Volume: ${token.volume24h} + - 🏦 Market Cap: ${token.marketCap} + `, + }, + KAI_ACTIONS.WOULD_LIKE_TO_DO_SOMETHING_ELSE, + KAI_ACTIONS.DO_SOMETHING_AFTER_CHECK_PRICE, + ] + + return [KAI_ACTIONS.TOKEN_NOT_FOUND] + }, + }, + ] + } + + return [KAI_ACTIONS.TOKEN_NOT_FOUND] + } catch (error) { + return [KAI_ACTIONS.ERROR] + } + }, + }, BACK_TO_MENU: { type: ActionType.OPTION, data: [KAI_OPTIONS.BACK_TO_MENU], response: (answer: string) => { - if (answer === KAI_OPTIONS.BACK_TO_MENU.title) return [KAI_ACTIONS.WELCOME] + if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] return [KAI_ACTIONS.INVALID] }, }, + DO_SOMETHING_AFTER_CHECK_PRICE: { + type: ActionType.OPTION, + data: [KAI_OPTIONS.BUY_TOKENS, KAI_OPTIONS.SELL_TOKENS, KAI_OPTIONS.BACK_TO_MENU], + response: (answer: string) => { + if ( + answer === KAI_OPTIONS.BUY_TOKENS.title.toLowerCase() || + answer === KAI_OPTIONS.SELL_TOKENS.title.toLowerCase() + ) + return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] + + if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] + return [KAI_ACTIONS.INVALID] + }, + }, + WOULD_LIKE_TO_DO_SOMETHING_ELSE: { + title: 'Would you like to do something else with this token?', + type: ActionType.TEXT, + }, COMING_SOON: { title: 'Coming soon, do you want to go back to the main menu?', type: ActionType.TEXT, @@ -99,4 +206,12 @@ export const KAI_ACTIONS: ListActions = { title: 'Invalid input, please follow the instruction!', type: ActionType.INVALID, }, + ERROR: { + title: 'Something went wrong, please try again!', + type: ActionType.INVALID, + }, + TOKEN_NOT_FOUND: { + title: 'I can not find your token, please enter others!', + type: ActionType.INVALID, + }, } diff --git a/src/components/Kai/styled.tsx b/src/components/Kai/styled.tsx index e827ccc118..89d0e27d6c 100644 --- a/src/components/Kai/styled.tsx +++ b/src/components/Kai/styled.tsx @@ -111,7 +111,7 @@ export const ChatInput = styled.input` } ` -export const SendIcon = styled(Send)` +export const SendIcon = styled(Send)<{ disabled: boolean }>` position: absolute; right: 12px; top: 12px; @@ -121,8 +121,14 @@ export const SendIcon = styled(Send)` :hover { color: ${({ theme }) => theme.primary}; - border-color: transparent; } + + ${({ disabled }) => + disabled && + css` + cursor: default; + color: transparent !important; + `} ` export const LoadingWrapper = styled.div` @@ -159,7 +165,7 @@ export const Loader = styled.div` ` export const ChatPanel = styled.div` - max-height: 400px; + max-height: 380px; overflow: scroll; ` From 0550ab565b79856b53ff89ee6835a9da36ff8a43 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 02:28:25 +0700 Subject: [PATCH 04/28] Change type to pass building process --- src/components/Kai/actions.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index 1623ef0d1e..dd88b580c8 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -16,7 +16,8 @@ export interface KaiAction { data?: KaiOption[] placeholder?: string loadingText?: string - response?: (answer: string) => KaiAction[] + // response?: (answer: string) => KaiAction[] + response?: any } export interface KaiOption { @@ -88,7 +89,7 @@ export const KAI_ACTIONS: ListActions = { title: 'Great! Which token are you interested in? Just type the name or address.', type: ActionType.TEXT, response: async (answer: string) => { - const filter = { + const filter: any = { chainId: 8453, // Base search: answer, page: '1', From 0a85de2b1dd872ef82962ff01df8ce456fc5e822 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 08:16:33 +0700 Subject: [PATCH 05/28] Re-build --- src/components/Kai/actions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index dd88b580c8..56b6f0090a 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -107,6 +107,7 @@ export const KAI_ACTIONS: ListActions = { ) const { data } = await res.json() console.log(data.assets) + console.log(123) if (data.assets.length === 1) { const token = data.assets[0] From 5602610dc7910310c6ddf613f2c883118923fb21 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 13:35:26 +0700 Subject: [PATCH 06/28] Disable chat input if the action is option type --- src/components/Kai/KaiPanel.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/Kai/KaiPanel.tsx b/src/components/Kai/KaiPanel.tsx index 84a5834eca..daeb6079e7 100644 --- a/src/components/Kai/KaiPanel.tsx +++ b/src/components/Kai/KaiPanel.tsx @@ -132,7 +132,11 @@ const KaiPanel = () => { {loading && } - + ) } From b08abf2d15e507d86ee7350d7b9a7ae5bb673410 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 15:19:07 +0700 Subject: [PATCH 07/28] Improve check token price --- src/components/Kai/KaiPanel.tsx | 64 ++++++++----- src/components/Kai/actions.ts | 163 ++++++++++++++++++++++++-------- src/components/Kai/styled.tsx | 6 +- 3 files changed, 171 insertions(+), 62 deletions(-) diff --git a/src/components/Kai/KaiPanel.tsx b/src/components/Kai/KaiPanel.tsx index daeb6079e7..adefedcaa6 100644 --- a/src/components/Kai/KaiPanel.tsx +++ b/src/components/Kai/KaiPanel.tsx @@ -1,6 +1,13 @@ +import { rgba } from 'polished' import { ChangeEvent, KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react' +import { Flex } from 'rebass' -import { ReactComponent as KaiAvatar } from 'assets/svg/kai_avatar2.svg' +import { ReactComponent as KaiAvatar } from 'assets/svg/kai_avatar.svg' +import { MouseoverTooltip } from 'components/Tooltip' +import { MAINNET_NETWORKS } from 'constants/networks' +import { useAllTokens } from 'hooks/Tokens' +import { NETWORKS_INFO } from 'hooks/useChainsConfig' +import useTheme from 'hooks/useTheme' import { ActionType, KAI_ACTIONS, KaiAction, KaiOption } from './actions' import { @@ -22,6 +29,7 @@ import { const DEFAULT_LOADING_TEXT = 'KAI is checking the data ...' const DEFAULT_CHAT_PLACEHOLDER_TEXT = 'Write a message...' +const DEFAULT_CHAIN_ID = 8453 const KaiPanel = () => { const chatPanelRef = useRef(null) @@ -30,23 +38,10 @@ const KaiPanel = () => { const [loading, setLoading] = useState(false) const [loadingText, setLoadingText] = useState(DEFAULT_LOADING_TEXT) const [listActions, setListActions] = useState([KAI_ACTIONS.MAIN_MENU]) - // const [listActions, setListActions] = useState([ - // KAI_ACTIONS.MAIN_MENU, - // KAI_ACTIONS.COMING_SOON, - // { - // title: 'Add liquidity', - // type: ActionType.USER_MESSAGE, - // }, - // { - // title: 'Add liquidity', - // type: ActionType.USER_MESSAGE, - // }, - // { - // title: 'Add liquidity', - // type: ActionType.USER_MESSAGE, - // }, - // KAI_ACTIONS.MAIN_MENU, - // ]) + const [chainId, setChainId] = useState(DEFAULT_CHAIN_ID) + + const whitelistTokens = useAllTokens(true, chainId) + const whitelistTokenAddress = useMemo(() => Object.keys(whitelistTokens), [whitelistTokens]) const lastAction = useMemo(() => { const cloneListActions = [...listActions] @@ -77,7 +72,8 @@ const KaiPanel = () => { const getActionResponse = async () => { const lastUserAction = listActions[listActions.length - 1] if (lastUserAction?.type === ActionType.USER_MESSAGE) { - const newActions: KaiAction[] = (await lastAction?.response?.(lastUserAction?.title?.toLowerCase() || '')) || [] + const newActions: KaiAction[] = + (await lastAction?.response?.(lastUserAction?.title?.toLowerCase() || '', chainId, whitelistTokenAddress)) || [] if (newActions.length) onChangeListActions(newActions) setLoading(false) setLoadingText(DEFAULT_LOADING_TEXT) @@ -101,7 +97,7 @@ const KaiPanel = () => { return ( <> - +
GM! What can I do for you today? πŸ‘‹
@@ -116,6 +112,8 @@ const KaiPanel = () => { ) : action.type === ActionType.TEXT || action.type === ActionType.INVALID ? ( {action.title} + ) : action.type === ActionType.HTML && action.title ? ( + ) : action.type === ActionType.USER_MESSAGE ? ( { ) } -const KaiHeader = () => { +const KaiHeader = ({ chainId, setChainId }: { chainId: number; setChainId: (value: number) => void }) => { + const theme = useTheme() + return ( <> - + I'm KAI Kyber Assistant Interface + + {MAINNET_NETWORKS.map(item => ( + + setChainId(item)} + sx={{ + background: chainId === item ? rgba(theme.primary, 0.2) : undefined, + border: chainId === item ? `1px solid ${theme.primary}` : 'none', + borderRadius: '4px', + }} + style={{ cursor: 'pointer' }} + > + + + + ))} + ) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index 56b6f0090a..7670f1a166 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -1,3 +1,5 @@ +import { formatDisplayNumber } from 'utils/numbers' + export enum Space { HALF_WIDTH = 'calc(50% - 6px)', FULL_WIDTH = '100%', @@ -8,6 +10,7 @@ export enum ActionType { OPTION, USER_MESSAGE, INVALID, + HTML, } export interface KaiAction { @@ -16,8 +19,7 @@ export interface KaiAction { data?: KaiOption[] placeholder?: string loadingText?: string - // response?: (answer: string) => KaiAction[] - response?: any + response?: (...args: any[]) => KaiAction[] | Promise } export interface KaiOption { @@ -88,14 +90,14 @@ export const KAI_ACTIONS: ListActions = { TYPE_TOKEN_TO_CHECK_PRICE: { title: 'Great! Which token are you interested in? Just type the name or address.', type: ActionType.TEXT, - response: async (answer: string) => { + response: async (answer: string, chainId: number, whitelistTokenAddress: string[]) => { const filter: any = { - chainId: 8453, // Base + chainId: chainId, search: answer, - page: '1', - // pageSize: 80, - pageSize: 5, - chainIds: 8453, // Base + page: 1, + pageSize: 100, + chainIds: chainId, + sort: '', } try { @@ -106,11 +108,21 @@ export const KAI_ACTIONS: ListActions = { }, ) const { data } = await res.json() - console.log(data.assets) - console.log(123) + const result = data.assets + .filter( + (token: any) => + token.marketCap && token.tokens.find((item: any) => whitelistTokenAddress.includes(item.address)), + ) + .map((token: any) => ({ + ...token, + token: token.tokens.find( + (item: any) => item.chainId === chainId.toString() && whitelistTokenAddress.includes(item.address), + ), + })) + .filter((token: any) => token.token) - if (data.assets.length === 1) { - const token = data.assets[0] + if (result.length === 1) { + const token = result[0] return [ { @@ -118,45 +130,118 @@ export const KAI_ACTIONS: ListActions = { type: ActionType.TEXT, }, { - type: ActionType.TEXT, + type: ActionType.HTML, title: ` - - πŸ“ˆ Price: ${token.tokens[0]?.priceBuy || ''} - - πŸ”„ 24h Price Change: ${-token.tokens[0]?.priceBuyChange24h} - - πŸ’Έ 24h Volume: ${token.volume24h} - - 🏦 Market Cap: ${token.marketCap} +
πŸ“ˆ Buy Price: ${ + token.token.priceBuy + ? formatDisplayNumber(token.token.priceBuy, { fractionDigits: 2, significantDigits: 7 }) + : '--' + }
+
πŸ“ˆ Sell Price: ${ + token.token.priceSell + ? formatDisplayNumber(token.token.priceSell, { fractionDigits: 2, significantDigits: 7 }) + : '--' + }
+
πŸ”„ 24h Buy Price Change: ${ + token.token.priceBuyChange24h + ? `${token.token.priceBuyChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(token.token.priceBuyChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + }
+
πŸ”„ 24h Sell Price Change: ${ + token.token.priceSellChange24h + ? `${token.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(token.token.priceSellChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + }
+
πŸ’Έ 24h Volume: ${ + token.volume24h + ? formatDisplayNumber(token.volume24h, { style: 'currency', fractionDigits: 2 }) + : '--' + }
+
🏦 Market Cap: ${ + token.marketCap + ? formatDisplayNumber(token.marketCap, { style: 'currency', fractionDigits: 2 }) + : '--' + }
`, }, KAI_ACTIONS.WOULD_LIKE_TO_DO_SOMETHING_ELSE, KAI_ACTIONS.DO_SOMETHING_AFTER_CHECK_PRICE, ] - } else if (data.assets.length > 1) { + } else if (result.length > 1) { return [ - { - title: `Here are the tokens I found. Which one do you mean?`, - type: ActionType.TEXT, - }, + KAI_ACTIONS.TOKEN_FOUND, { type: ActionType.OPTION, - data: data.assets.map((item: any) => ({ + data: result.map((item: any) => ({ title: item.name, - space: Space.HALF_WIDTH, + space: item.name.length <= 10 ? Space.HALF_WIDTH : Space.FULL_WIDTH, })), - response: (innerAnswer: string) => { - const token = data.assets.find((item: any) => item.name.toLowerCase() === innerAnswer) + response: (tokenNameSelected: string) => { + const token = result.find((item: any) => item.name.toLowerCase() === tokenNameSelected) if (token) return [ { - title: `Here’s what I’ve got for ${innerAnswer}`, + title: `Here’s what I’ve got for ${tokenNameSelected}`, type: ActionType.TEXT, }, { - type: ActionType.TEXT, + type: ActionType.HTML, title: ` - - πŸ“ˆ Price: ${token.tokens[0]?.priceBuy || ''} - - πŸ”„ 24h Price Change: ${-token.tokens[0]?.priceBuyChange24h} - - πŸ’Έ 24h Volume: ${token.volume24h} - - 🏦 Market Cap: ${token.marketCap} - `, +
πŸ“ˆ Buy Price: ${ + token.token.priceBuy + ? formatDisplayNumber(token.token.priceBuy, { fractionDigits: 2, significantDigits: 7 }) + : '--' + }
+
πŸ“ˆ Sell Price: ${ + token.token.priceSell + ? formatDisplayNumber(token.token.priceSell, { fractionDigits: 2, significantDigits: 7 }) + : '--' + }
+
πŸ”„ 24h Buy Price Change: ${ + token.token.priceBuyChange24h + ? `${token.token.priceBuyChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(token.token.priceBuyChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + }
+
πŸ”„ 24h Sell Price Change: ${ + token.token.priceSellChange24h + ? `${token.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(token.token.priceSellChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + }
+
πŸ’Έ 24h Volume: ${ + token.volume24h + ? formatDisplayNumber(token.volume24h, { style: 'currency', fractionDigits: 2 }) + : '--' + }
+
🏦 Market Cap: ${ + token.marketCap + ? formatDisplayNumber(token.marketCap, { style: 'currency', fractionDigits: 2 }) + : '--' + }
+ `, }, KAI_ACTIONS.WOULD_LIKE_TO_DO_SOMETHING_ELSE, KAI_ACTIONS.DO_SOMETHING_AFTER_CHECK_PRICE, @@ -201,19 +286,23 @@ export const KAI_ACTIONS: ListActions = { type: ActionType.TEXT, }, COMING_SOON: { - title: 'Coming soon, do you want to go back to the main menu?', + title: 'πŸƒπŸ» Coming soon, do you want to go back to the main menu?', type: ActionType.TEXT, }, INVALID: { - title: 'Invalid input, please follow the instruction!', + title: '❌ Invalid input, please follow the instruction!', type: ActionType.INVALID, }, ERROR: { - title: 'Something went wrong, please try again!', + title: '❌ Something went wrong, please try again!', type: ActionType.INVALID, }, TOKEN_NOT_FOUND: { - title: 'I can not find your token, please enter others!', + title: 'πŸ”­ I can not find your token, please enter others!', type: ActionType.INVALID, }, + TOKEN_FOUND: { + title: 'πŸ‘€ Here are the tokens I found. Which one do you mean?', + type: ActionType.TEXT, + }, } diff --git a/src/components/Kai/styled.tsx b/src/components/Kai/styled.tsx index 89d0e27d6c..125009fb92 100644 --- a/src/components/Kai/styled.tsx +++ b/src/components/Kai/styled.tsx @@ -165,7 +165,7 @@ export const Loader = styled.div` ` export const ChatPanel = styled.div` - max-height: 380px; + max-height: 360px; overflow: scroll; ` @@ -184,12 +184,12 @@ export const ActionButton = styled.div<{ width: string }>` border-radius: 8px; color: #fafafa; height: 36px; - background-color: ${({ theme }) => rgba(theme.white, 0.04)}; + background-color: ${({ theme }) => rgba(theme.primary, 0.1)}; transition: 0.1s ease-in-out; cursor: pointer; :hover { - background-color: ${({ theme }) => rgba(theme.white, 0.08)}; + background-color: ${({ theme }) => rgba(theme.primary, 0.18)}; } ${({ width }) => From 1060dde9134e91631f02ca1d9c11feb614f55034 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 16:50:12 +0700 Subject: [PATCH 08/28] Change color of action button, sort tokens by market cap, show symbol of token instead of name, change default chain --- src/components/Kai/KaiPanel.tsx | 23 +++++++++++++++----- src/components/Kai/actions.ts | 37 ++++++++++++++++++++++----------- src/components/Kai/styled.tsx | 12 +++++++++-- 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/components/Kai/KaiPanel.tsx b/src/components/Kai/KaiPanel.tsx index adefedcaa6..6271c8e3e6 100644 --- a/src/components/Kai/KaiPanel.tsx +++ b/src/components/Kai/KaiPanel.tsx @@ -21,6 +21,7 @@ import { KaiHeaderWrapper, Loader, LoadingWrapper, + MainActionButton, SendIcon, SubTextSpan, UserMessage, @@ -29,7 +30,7 @@ import { const DEFAULT_LOADING_TEXT = 'KAI is checking the data ...' const DEFAULT_CHAT_PLACEHOLDER_TEXT = 'Write a message...' -const DEFAULT_CHAIN_ID = 8453 +const DEFAULT_CHAIN_ID = 1 const KaiPanel = () => { const chatPanelRef = useRef(null) @@ -48,7 +49,10 @@ const KaiPanel = () => { cloneListActions.reverse() return cloneListActions.find( - (action: KaiAction) => action.type !== ActionType.INVALID && action.type !== ActionType.USER_MESSAGE, + (action: KaiAction) => + action.type !== ActionType.INVALID && + action.type !== ActionType.INVALID_AND_BACK && + action.type !== ActionType.USER_MESSAGE, ) }, [listActions]) @@ -94,6 +98,7 @@ const KaiPanel = () => { getActionResponse() // eslint-disable-next-line react-hooks/exhaustive-deps }, [listActions]) + console.log('lastAction', lastAction) return ( <> @@ -102,7 +107,15 @@ const KaiPanel = () => {
GM! What can I do for you today? πŸ‘‹
{listActions.map((action: KaiAction, index: number) => - action.type === ActionType.OPTION ? ( + action.type === ActionType.MAIN_OPTION ? ( + + {action.data?.map((option: KaiOption, optionIndex: number) => ( + onSubmitChat(option.title)}> + {option.title} + + ))} + + ) : action.type === ActionType.OPTION || action.type === ActionType.INVALID_AND_BACK ? ( {action.data?.map((option: KaiOption, optionIndex: number) => ( onSubmitChat(option.title)}> @@ -131,7 +144,7 @@ const KaiPanel = () => { {loading && } @@ -149,7 +162,7 @@ const KaiHeader = ({ chainId, setChainId }: { chainId: number; setChainId: (valu I'm KAI Kyber Assistant Interface - + {MAINNET_NETWORKS.map(item => ( { @@ -88,9 +90,11 @@ export const KAI_ACTIONS: ListActions = { }, }, TYPE_TOKEN_TO_CHECK_PRICE: { - title: 'Great! Which token are you interested in? Just type the name or address.', + title: '🫑 Great! Which token are you interested in? Just type the name or address.', type: ActionType.TEXT, response: async (answer: string, chainId: number, whitelistTokenAddress: string[]) => { + if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] + const filter: any = { chainId: chainId, search: answer, @@ -120,6 +124,7 @@ export const KAI_ACTIONS: ListActions = { ), })) .filter((token: any) => token.token) + .sort((a: any, b: any) => b.marketCap - a.marketCap) if (result.length === 1) { const token = result[0] @@ -184,16 +189,20 @@ export const KAI_ACTIONS: ListActions = { KAI_ACTIONS.TOKEN_FOUND, { type: ActionType.OPTION, - data: result.map((item: any) => ({ - title: item.name, - space: item.name.length <= 10 ? Space.HALF_WIDTH : Space.FULL_WIDTH, - })), - response: (tokenNameSelected: string) => { - const token = result.find((item: any) => item.name.toLowerCase() === tokenNameSelected) + data: result + .map((item: any) => ({ + title: item.symbol, + space: item.symbol.length <= 10 ? Space.HALF_WIDTH : Space.FULL_WIDTH, + })) + .concat(KAI_ACTIONS.INVALID_BACK_TO_MENU.data), + response: (tokenSymbolSelected: string) => { + if (tokenSymbolSelected === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] + + const token = result.find((item: any) => item.symbol.toLowerCase() === tokenSymbolSelected) if (token) return [ { - title: `Here’s what I’ve got for ${tokenNameSelected}`, + title: `Here’s what I’ve got for ${tokenSymbolSelected}`, type: ActionType.TEXT, }, { @@ -247,15 +256,15 @@ export const KAI_ACTIONS: ListActions = { KAI_ACTIONS.DO_SOMETHING_AFTER_CHECK_PRICE, ] - return [KAI_ACTIONS.TOKEN_NOT_FOUND] + return [KAI_ACTIONS.TOKEN_NOT_FOUND, KAI_ACTIONS.INVALID_BACK_TO_MENU] }, }, ] } - return [KAI_ACTIONS.TOKEN_NOT_FOUND] + return [KAI_ACTIONS.TOKEN_NOT_FOUND, KAI_ACTIONS.INVALID_BACK_TO_MENU] } catch (error) { - return [KAI_ACTIONS.ERROR] + return [KAI_ACTIONS.ERROR, KAI_ACTIONS.INVALID_BACK_TO_MENU] } }, }, @@ -267,6 +276,10 @@ export const KAI_ACTIONS: ListActions = { return [KAI_ACTIONS.INVALID] }, }, + INVALID_BACK_TO_MENU: { + type: ActionType.INVALID_AND_BACK, + data: [KAI_OPTIONS.BACK_TO_MENU], + }, DO_SOMETHING_AFTER_CHECK_PRICE: { type: ActionType.OPTION, data: [KAI_OPTIONS.BUY_TOKENS, KAI_OPTIONS.SELL_TOKENS, KAI_OPTIONS.BACK_TO_MENU], diff --git a/src/components/Kai/styled.tsx b/src/components/Kai/styled.tsx index 125009fb92..4cb6130783 100644 --- a/src/components/Kai/styled.tsx +++ b/src/components/Kai/styled.tsx @@ -184,12 +184,12 @@ export const ActionButton = styled.div<{ width: string }>` border-radius: 8px; color: #fafafa; height: 36px; - background-color: ${({ theme }) => rgba(theme.primary, 0.1)}; + background-color: ${({ theme }) => rgba(theme.white, 0.04)}; transition: 0.1s ease-in-out; cursor: pointer; :hover { - background-color: ${({ theme }) => rgba(theme.primary, 0.18)}; + background-color: ${({ theme }) => rgba(theme.white, 0.08)}; } ${({ width }) => @@ -198,6 +198,14 @@ export const ActionButton = styled.div<{ width: string }>` `} ` +export const MainActionButton = styled(ActionButton)` + background-color: ${({ theme }) => rgba(theme.primary, 0.1)}; + + :hover { + background-color: ${({ theme }) => rgba(theme.primary, 0.18)}; + } +` + export const ActionText = styled.div` margin-top: 16px; ` From 3d61c78bb0f06111d0214ef8478e3ed5b14e3fd2 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 16:58:08 +0700 Subject: [PATCH 09/28] Change chat panel width --- src/components/Kai/KaiPanel.tsx | 1 - src/components/Kai/actions.ts | 100 ++++++++++++++++---------------- src/components/Kai/styled.tsx | 2 +- 3 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/components/Kai/KaiPanel.tsx b/src/components/Kai/KaiPanel.tsx index 6271c8e3e6..639c56c496 100644 --- a/src/components/Kai/KaiPanel.tsx +++ b/src/components/Kai/KaiPanel.tsx @@ -98,7 +98,6 @@ const KaiPanel = () => { getActionResponse() // eslint-disable-next-line react-hooks/exhaustive-deps }, [listActions]) - console.log('lastAction', lastAction) return ( <> diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index 8745dc162a..cb659157f5 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -89,6 +89,56 @@ export const KAI_ACTIONS: ListActions = { return [KAI_ACTIONS.INVALID] }, }, + BACK_TO_MENU: { + type: ActionType.OPTION, + data: [KAI_OPTIONS.BACK_TO_MENU], + response: (answer: string) => { + if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] + return [KAI_ACTIONS.INVALID] + }, + }, + INVALID_BACK_TO_MENU: { + type: ActionType.INVALID_AND_BACK, + data: [KAI_OPTIONS.BACK_TO_MENU], + }, + DO_SOMETHING_AFTER_CHECK_PRICE: { + type: ActionType.OPTION, + data: [KAI_OPTIONS.BUY_TOKENS, KAI_OPTIONS.SELL_TOKENS, KAI_OPTIONS.BACK_TO_MENU], + response: (answer: string) => { + if ( + answer === KAI_OPTIONS.BUY_TOKENS.title.toLowerCase() || + answer === KAI_OPTIONS.SELL_TOKENS.title.toLowerCase() + ) + return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] + + if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] + return [KAI_ACTIONS.INVALID] + }, + }, + WOULD_LIKE_TO_DO_SOMETHING_ELSE: { + title: 'Would you like to do something else with this token?', + type: ActionType.TEXT, + }, + COMING_SOON: { + title: 'πŸƒπŸ» Coming soon, do you want to go back to the main menu?', + type: ActionType.TEXT, + }, + INVALID: { + title: '❌ Invalid input, please follow the instruction!', + type: ActionType.INVALID, + }, + ERROR: { + title: '❌ Something went wrong, please try again!', + type: ActionType.INVALID, + }, + TOKEN_NOT_FOUND: { + title: 'πŸ”­ I can not find your token, please enter others!', + type: ActionType.INVALID, + }, + TOKEN_FOUND: { + title: 'πŸ‘€ Here are the tokens I found. Which one do you mean?', + type: ActionType.TEXT, + }, TYPE_TOKEN_TO_CHECK_PRICE: { title: '🫑 Great! Which token are you interested in? Just type the name or address.', type: ActionType.TEXT, @@ -268,54 +318,4 @@ export const KAI_ACTIONS: ListActions = { } }, }, - BACK_TO_MENU: { - type: ActionType.OPTION, - data: [KAI_OPTIONS.BACK_TO_MENU], - response: (answer: string) => { - if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] - return [KAI_ACTIONS.INVALID] - }, - }, - INVALID_BACK_TO_MENU: { - type: ActionType.INVALID_AND_BACK, - data: [KAI_OPTIONS.BACK_TO_MENU], - }, - DO_SOMETHING_AFTER_CHECK_PRICE: { - type: ActionType.OPTION, - data: [KAI_OPTIONS.BUY_TOKENS, KAI_OPTIONS.SELL_TOKENS, KAI_OPTIONS.BACK_TO_MENU], - response: (answer: string) => { - if ( - answer === KAI_OPTIONS.BUY_TOKENS.title.toLowerCase() || - answer === KAI_OPTIONS.SELL_TOKENS.title.toLowerCase() - ) - return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] - - if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] - return [KAI_ACTIONS.INVALID] - }, - }, - WOULD_LIKE_TO_DO_SOMETHING_ELSE: { - title: 'Would you like to do something else with this token?', - type: ActionType.TEXT, - }, - COMING_SOON: { - title: 'πŸƒπŸ» Coming soon, do you want to go back to the main menu?', - type: ActionType.TEXT, - }, - INVALID: { - title: '❌ Invalid input, please follow the instruction!', - type: ActionType.INVALID, - }, - ERROR: { - title: '❌ Something went wrong, please try again!', - type: ActionType.INVALID, - }, - TOKEN_NOT_FOUND: { - title: 'πŸ”­ I can not find your token, please enter others!', - type: ActionType.INVALID, - }, - TOKEN_FOUND: { - title: 'πŸ‘€ Here are the tokens I found. Which one do you mean?', - type: ActionType.TEXT, - }, } diff --git a/src/components/Kai/styled.tsx b/src/components/Kai/styled.tsx index 4cb6130783..7f32623133 100644 --- a/src/components/Kai/styled.tsx +++ b/src/components/Kai/styled.tsx @@ -41,7 +41,7 @@ export const ModalContent = styled.div` background: ${({ theme }) => theme.tableHeader}; padding: 20px 24px 26px; border-radius: 12px; - width: 320px; + width: 348px; ${({ theme }) => theme.mediaWidth.upToExtraSmall` width: calc(100vw - 2rem); From fb369b7f63fce5726033f08f99776b49f138afab Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 18:52:21 +0700 Subject: [PATCH 10/28] Add 'See market trends' action flow --- src/components/Kai/KaiPanel.tsx | 7 +- src/components/Kai/actions.ts | 250 ++++++++++++++++++++++++++++---- 2 files changed, 231 insertions(+), 26 deletions(-) diff --git a/src/components/Kai/KaiPanel.tsx b/src/components/Kai/KaiPanel.tsx index 639c56c496..abd74393f0 100644 --- a/src/components/Kai/KaiPanel.tsx +++ b/src/components/Kai/KaiPanel.tsx @@ -77,7 +77,12 @@ const KaiPanel = () => { const lastUserAction = listActions[listActions.length - 1] if (lastUserAction?.type === ActionType.USER_MESSAGE) { const newActions: KaiAction[] = - (await lastAction?.response?.(lastUserAction?.title?.toLowerCase() || '', chainId, whitelistTokenAddress)) || [] + (await lastAction?.response?.({ + answer: lastUserAction?.title?.toLowerCase() || '', + chainId, + whitelistTokenAddress, + arg: lastAction.arg, + })) || [] if (newActions.length) onChangeListActions(newActions) setLoading(false) setLoadingText(DEFAULT_LOADING_TEXT) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index cb659157f5..108ac33d63 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -3,6 +3,7 @@ import { formatDisplayNumber } from 'utils/numbers' export enum Space { HALF_WIDTH = 'calc(50% - 6px)', FULL_WIDTH = '100%', + ONE_THIRD_WIDTH = 'calc((100% - 24px) / 3)', } export enum ActionType { @@ -19,6 +20,7 @@ export interface KaiAction { title?: string type: ActionType data?: KaiOption[] + arg?: any placeholder?: string loadingText?: string response?: (...args: any[]) => KaiAction[] | Promise @@ -50,20 +52,28 @@ const KAI_OPTIONS: ListOptions = { title: 'Find high APY pools', space: Space.FULL_WIDTH, }, - BUY_TOKENS: { - title: 'Buy tokens', - space: Space.HALF_WIDTH, - }, - SELL_TOKENS: { - title: 'Sell tokens', - space: Space.HALF_WIDTH, + SWAP_TOKEN: { + title: 'Buy/Sell tokens', + space: Space.FULL_WIDTH, }, ADD_LIQUIDITY: { title: 'Add liquidity', space: Space.FULL_WIDTH, }, + TOP_BIG_SPREAD: { + title: 'Top 24h Big Spread', + space: Space.FULL_WIDTH, + }, + TOP_GAINERS: { + title: 'Top 24h Gainers', + space: Space.HALF_WIDTH, + }, + TOP_VOLUME: { + title: 'Top 24h Volume', + space: Space.HALF_WIDTH, + }, BACK_TO_MENU: { - title: 'Back to the main menu', + title: '↩ Back to the main menu', space: Space.FULL_WIDTH, }, } @@ -72,8 +82,7 @@ export const MAIN_MENU: KaiOption[] = [ KAI_OPTIONS.CHECK_TOKEN_PRICE, KAI_OPTIONS.SEE_MARKET_TRENDS, KAI_OPTIONS.FIND_HIGH_APY_POOLS, - KAI_OPTIONS.BUY_TOKENS, - KAI_OPTIONS.SELL_TOKENS, + KAI_OPTIONS.SWAP_TOKEN, KAI_OPTIONS.ADD_LIQUIDITY, ] @@ -82,8 +91,10 @@ export const KAI_ACTIONS: ListActions = { type: ActionType.MAIN_OPTION, data: MAIN_MENU, placeholder: 'Ask me anything or select...', - response: (answer: string) => { + response: ({ answer }: { answer: string }) => { if (answer === KAI_OPTIONS.CHECK_TOKEN_PRICE.title.toLowerCase()) return [KAI_ACTIONS.TYPE_TOKEN_TO_CHECK_PRICE] + if (answer === KAI_OPTIONS.SEE_MARKET_TRENDS.title.toLowerCase()) + return [KAI_ACTIONS.SEE_MARKET_TRENDS_WELCOME, KAI_ACTIONS.SEE_MARKET_TRENDS] if (MAIN_MENU.find((option: KaiOption) => answer.trim().toLowerCase() === option.title.toLowerCase())) return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] return [KAI_ACTIONS.INVALID] @@ -92,7 +103,7 @@ export const KAI_ACTIONS: ListActions = { BACK_TO_MENU: { type: ActionType.OPTION, data: [KAI_OPTIONS.BACK_TO_MENU], - response: (answer: string) => { + response: ({ answer }: { answer: string }) => { if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] return [KAI_ACTIONS.INVALID] }, @@ -103,14 +114,10 @@ export const KAI_ACTIONS: ListActions = { }, DO_SOMETHING_AFTER_CHECK_PRICE: { type: ActionType.OPTION, - data: [KAI_OPTIONS.BUY_TOKENS, KAI_OPTIONS.SELL_TOKENS, KAI_OPTIONS.BACK_TO_MENU], - response: (answer: string) => { - if ( - answer === KAI_OPTIONS.BUY_TOKENS.title.toLowerCase() || - answer === KAI_OPTIONS.SELL_TOKENS.title.toLowerCase() - ) + data: [KAI_OPTIONS.SWAP_TOKEN, KAI_OPTIONS.BACK_TO_MENU], + response: ({ answer }: { answer: string }) => { + if (answer === KAI_OPTIONS.SWAP_TOKEN.title.toLowerCase()) return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] - if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] return [KAI_ACTIONS.INVALID] }, @@ -142,7 +149,15 @@ export const KAI_ACTIONS: ListActions = { TYPE_TOKEN_TO_CHECK_PRICE: { title: '🫑 Great! Which token are you interested in? Just type the name or address.', type: ActionType.TEXT, - response: async (answer: string, chainId: number, whitelistTokenAddress: string[]) => { + response: async ({ + answer, + chainId, + whitelistTokenAddress, + }: { + answer: string + chainId: number + whitelistTokenAddress: string[] + }) => { if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] const filter: any = { @@ -189,12 +204,18 @@ export const KAI_ACTIONS: ListActions = { title: `
πŸ“ˆ Buy Price: ${ token.token.priceBuy - ? formatDisplayNumber(token.token.priceBuy, { fractionDigits: 2, significantDigits: 7 }) + ? formatDisplayNumber(token.token.priceBuy, { + fractionDigits: 2, + significantDigits: 7, + }) : '--' }
πŸ“ˆ Sell Price: ${ token.token.priceSell - ? formatDisplayNumber(token.token.priceSell, { fractionDigits: 2, significantDigits: 7 }) + ? formatDisplayNumber(token.token.priceSell, { + fractionDigits: 2, + significantDigits: 7, + }) : '--' }
πŸ”„ 24h Buy Price Change: ${ @@ -245,7 +266,7 @@ export const KAI_ACTIONS: ListActions = { space: item.symbol.length <= 10 ? Space.HALF_WIDTH : Space.FULL_WIDTH, })) .concat(KAI_ACTIONS.INVALID_BACK_TO_MENU.data), - response: (tokenSymbolSelected: string) => { + response: ({ answer: tokenSymbolSelected }: { answer: string }) => { if (tokenSymbolSelected === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] const token = result.find((item: any) => item.symbol.toLowerCase() === tokenSymbolSelected) @@ -260,12 +281,18 @@ export const KAI_ACTIONS: ListActions = { title: `
πŸ“ˆ Buy Price: ${ token.token.priceBuy - ? formatDisplayNumber(token.token.priceBuy, { fractionDigits: 2, significantDigits: 7 }) + ? formatDisplayNumber(token.token.priceBuy, { + fractionDigits: 2, + significantDigits: 7, + }) : '--' }
πŸ“ˆ Sell Price: ${ token.token.priceSell - ? formatDisplayNumber(token.token.priceSell, { fractionDigits: 2, significantDigits: 7 }) + ? formatDisplayNumber(token.token.priceSell, { + fractionDigits: 2, + significantDigits: 7, + }) : '--' }
πŸ”„ 24h Buy Price Change: ${ @@ -318,4 +345,177 @@ export const KAI_ACTIONS: ListActions = { } }, }, + SEE_MARKET_TRENDS_WELCOME: { + title: '🫑 Got it! What would you like to see the trend in 24 hours?', + type: ActionType.TEXT, + }, + SEE_MARKET_TRENDS: { + type: ActionType.OPTION, + data: [KAI_OPTIONS.TOP_BIG_SPREAD, KAI_OPTIONS.TOP_GAINERS, KAI_OPTIONS.TOP_VOLUME, KAI_OPTIONS.BACK_TO_MENU], + response: ({ answer }: { answer: string }) => { + if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] + if (answer === KAI_OPTIONS.TOP_BIG_SPREAD.title.toLowerCase()) + return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] + if ( + answer === KAI_OPTIONS.TOP_GAINERS.title.toLowerCase() || + answer === KAI_OPTIONS.TOP_VOLUME.title.toLowerCase() + ) + return [{ ...KAI_ACTIONS.SEE_MARKET_TRENDS_CHOOSE_AMOUNT, arg: answer }] + + return [KAI_ACTIONS.INVALID] + }, + }, + SEE_MARKET_TRENDS_CHOOSE_AMOUNT: { + type: ActionType.OPTION, + data: [10, 15, 20].map((item: number) => ({ title: item.toString(), space: Space.ONE_THIRD_WIDTH })), + response: async ({ answer, chainId, arg }: { answer: string; chainId: number; arg: any }) => { + const filter: any = { + chainId: chainId, + search: '', + page: 1, + pageSize: answer, + chainIds: chainId, + sort: + arg === KAI_OPTIONS.TOP_GAINERS.title.toLowerCase() + ? 'price_sell_change_24h-1 desc' + : arg === KAI_OPTIONS.TOP_VOLUME.title.toLowerCase() + ? 'volume_24h desc' + : '', + } + + try { + const res = await fetch( + `${import.meta.env.VITE_TOKEN_API_URL}/v1/public/assets?` + new URLSearchParams(filter).toString(), + { + method: 'GET', + }, + ) + const { data } = await res.json() + const result = data.assets.map((token: any) => ({ + ...token, + token: token.tokens.find((item: any) => item.chainId === chainId.toString()), + })) + + const resultToActionData = result.map((item: any) => { + const price = item.token.priceSell + + const priceSellChange24h = item.token.priceSellChange24h + ? `${item.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(item.token.priceSellChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + const volume24h = item.volume24h + ? formatDisplayNumber(item.volume24h, { style: 'currency', fractionDigits: 2 }) + : '--' + const metricValue = + arg === KAI_OPTIONS.TOP_GAINERS.title.toLowerCase() + ? priceSellChange24h + : arg === KAI_OPTIONS.TOP_VOLUME.title.toLowerCase() + ? volume24h + : '' + return { + title: `πŸ’Έ ${item.symbol} - ${metricValue} - ${ + price + ? formatDisplayNumber(price, { + fractionDigits: 2, + significantDigits: 7, + }) + : '--' + }`, + space: Space.FULL_WIDTH, + } + }) + + return [ + { + title: `Here’s the list of ${arg + .replace('24h', '') + .trim()} for the last 24h, click on a token to see more details!`, + type: ActionType.TEXT, + }, + { + type: ActionType.OPTION, + data: resultToActionData.concat([KAI_OPTIONS.SWAP_TOKEN, KAI_OPTIONS.BACK_TO_MENU]), + response: ({ answer }: { answer: string }) => { + if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] + if (answer === KAI_OPTIONS.SWAP_TOKEN.title.toLowerCase()) + return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] + + const index = resultToActionData.findIndex((item: KaiOption) => item.title.toLowerCase() === answer) + + if (index > 1) { + const token = result[index] + + return [ + { + type: ActionType.HTML, + title: ` +
πŸ“ˆ Buy Price: ${ + token.token.priceBuy + ? formatDisplayNumber(token.token.priceBuy, { + fractionDigits: 2, + significantDigits: 7, + }) + : '--' + }
+
πŸ“ˆ Sell Price: ${ + token.token.priceSell + ? formatDisplayNumber(token.token.priceSell, { + fractionDigits: 2, + significantDigits: 7, + }) + : '--' + }
+
πŸ”„ 24h Buy Price Change: ${ + token.token.priceBuyChange24h + ? `${token.token.priceBuyChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(token.token.priceBuyChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + }
+
πŸ”„ 24h Sell Price Change: ${ + token.token.priceSellChange24h + ? `${token.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(token.token.priceSellChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + }
+
πŸ’Έ 24h Volume: ${ + token.volume24h + ? formatDisplayNumber(token.volume24h, { style: 'currency', fractionDigits: 2 }) + : '--' + }
+
🏦 Market Cap: ${ + token.marketCap + ? formatDisplayNumber(token.marketCap, { style: 'currency', fractionDigits: 2 }) + : '--' + }
+ `, + }, + KAI_ACTIONS.WOULD_LIKE_TO_DO_SOMETHING_ELSE, + KAI_ACTIONS.DO_SOMETHING_AFTER_CHECK_PRICE, + ] + } + + return [KAI_ACTIONS.INVALID, KAI_ACTIONS.INVALID_BACK_TO_MENU] + }, + }, + ] + } catch (error) { + return [KAI_ACTIONS.ERROR, KAI_ACTIONS.INVALID_BACK_TO_MENU] + } + }, + }, } From 16156b3e0c887322a911ae5cd4a2b6f56bcf7492 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 19:13:42 +0700 Subject: [PATCH 11/28] Add quote symbol by chain for price --- src/components/Kai/KaiPanel.tsx | 8 +++++++ src/components/Kai/actions.ts | 42 +++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/components/Kai/KaiPanel.tsx b/src/components/Kai/KaiPanel.tsx index abd74393f0..777fada444 100644 --- a/src/components/Kai/KaiPanel.tsx +++ b/src/components/Kai/KaiPanel.tsx @@ -1,6 +1,7 @@ import { rgba } from 'polished' import { ChangeEvent, KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react' import { Flex } from 'rebass' +import { useGetQuoteByChainQuery } from 'services/marketOverview' import { ReactComponent as KaiAvatar } from 'assets/svg/kai_avatar.svg' import { MouseoverTooltip } from 'components/Tooltip' @@ -44,6 +45,12 @@ const KaiPanel = () => { const whitelistTokens = useAllTokens(true, chainId) const whitelistTokenAddress = useMemo(() => Object.keys(whitelistTokens), [whitelistTokens]) + const { data: quoteData } = useGetQuoteByChainQuery() + const quoteSymbol = useMemo( + () => quoteData?.data?.onchainPrice?.usdQuoteTokenByChainId?.[chainId || 1]?.symbol, + [chainId, quoteData], + ) + const lastAction = useMemo(() => { const cloneListActions = [...listActions] cloneListActions.reverse() @@ -82,6 +89,7 @@ const KaiPanel = () => { chainId, whitelistTokenAddress, arg: lastAction.arg, + quoteSymbol, })) || [] if (newActions.length) onChangeListActions(newActions) setLoading(false) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index 108ac33d63..f6bacf4e18 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -153,10 +153,12 @@ export const KAI_ACTIONS: ListActions = { answer, chainId, whitelistTokenAddress, + quoteSymbol, }: { answer: string chainId: number whitelistTokenAddress: string[] + quoteSymbol: string }) => { if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] @@ -164,7 +166,7 @@ export const KAI_ACTIONS: ListActions = { chainId: chainId, search: answer, page: 1, - pageSize: 100, + pageSize: 50, chainIds: chainId, sort: '', } @@ -204,18 +206,18 @@ export const KAI_ACTIONS: ListActions = { title: `
πŸ“ˆ Buy Price: ${ token.token.priceBuy - ? formatDisplayNumber(token.token.priceBuy, { + ? `${formatDisplayNumber(token.token.priceBuy, { fractionDigits: 2, significantDigits: 7, - }) + })} ${quoteSymbol}` : '--' }
πŸ“ˆ Sell Price: ${ token.token.priceSell - ? formatDisplayNumber(token.token.priceSell, { + ? `${formatDisplayNumber(token.token.priceSell, { fractionDigits: 2, significantDigits: 7, - }) + })} ${quoteSymbol}` : '--' }
πŸ”„ 24h Buy Price Change: ${ @@ -281,18 +283,18 @@ export const KAI_ACTIONS: ListActions = { title: `
πŸ“ˆ Buy Price: ${ token.token.priceBuy - ? formatDisplayNumber(token.token.priceBuy, { + ? `${formatDisplayNumber(token.token.priceBuy, { fractionDigits: 2, significantDigits: 7, - }) + })} ${quoteSymbol}` : '--' }
πŸ“ˆ Sell Price: ${ token.token.priceSell - ? formatDisplayNumber(token.token.priceSell, { + ? `${formatDisplayNumber(token.token.priceSell, { fractionDigits: 2, significantDigits: 7, - }) + })} ${quoteSymbol}` : '--' }
πŸ”„ 24h Buy Price Change: ${ @@ -368,7 +370,17 @@ export const KAI_ACTIONS: ListActions = { SEE_MARKET_TRENDS_CHOOSE_AMOUNT: { type: ActionType.OPTION, data: [10, 15, 20].map((item: number) => ({ title: item.toString(), space: Space.ONE_THIRD_WIDTH })), - response: async ({ answer, chainId, arg }: { answer: string; chainId: number; arg: any }) => { + response: async ({ + answer, + chainId, + arg, + quoteSymbol, + }: { + answer: string + chainId: number + arg: any + quoteSymbol: string + }) => { const filter: any = { chainId: chainId, search: '', @@ -420,10 +432,10 @@ export const KAI_ACTIONS: ListActions = { return { title: `πŸ’Έ ${item.symbol} - ${metricValue} - ${ price - ? formatDisplayNumber(price, { + ? `${formatDisplayNumber(price, { fractionDigits: 2, significantDigits: 7, - }) + })} ${quoteSymbol}` : '--' }`, space: Space.FULL_WIDTH, @@ -440,7 +452,7 @@ export const KAI_ACTIONS: ListActions = { { type: ActionType.OPTION, data: resultToActionData.concat([KAI_OPTIONS.SWAP_TOKEN, KAI_OPTIONS.BACK_TO_MENU]), - response: ({ answer }: { answer: string }) => { + response: ({ answer, quoteSymbol }: { answer: string; quoteSymbol: string }) => { if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] if (answer === KAI_OPTIONS.SWAP_TOKEN.title.toLowerCase()) return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] @@ -456,10 +468,10 @@ export const KAI_ACTIONS: ListActions = { title: `
πŸ“ˆ Buy Price: ${ token.token.priceBuy - ? formatDisplayNumber(token.token.priceBuy, { + ? `${formatDisplayNumber(token.token.priceBuy, { fractionDigits: 2, significantDigits: 7, - }) + })} ${quoteSymbol}` : '--' }
πŸ“ˆ Sell Price: ${ From cf983dc031a91484b4a778f0e9e2ca7e087326cf Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 20:13:40 +0700 Subject: [PATCH 12/28] Change panel max-height and fix coming soon action --- src/components/Kai/actions.ts | 16 ++++++++-------- src/components/Kai/styled.tsx | 4 ++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index f6bacf4e18..eec39967fc 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -96,7 +96,7 @@ export const KAI_ACTIONS: ListActions = { if (answer === KAI_OPTIONS.SEE_MARKET_TRENDS.title.toLowerCase()) return [KAI_ACTIONS.SEE_MARKET_TRENDS_WELCOME, KAI_ACTIONS.SEE_MARKET_TRENDS] if (MAIN_MENU.find((option: KaiOption) => answer.trim().toLowerCase() === option.title.toLowerCase())) - return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] + return [KAI_ACTIONS.COMING_SOON] return [KAI_ACTIONS.INVALID] }, }, @@ -117,7 +117,7 @@ export const KAI_ACTIONS: ListActions = { data: [KAI_OPTIONS.SWAP_TOKEN, KAI_OPTIONS.BACK_TO_MENU], response: ({ answer }: { answer: string }) => { if (answer === KAI_OPTIONS.SWAP_TOKEN.title.toLowerCase()) - return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] + return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.INVALID_BACK_TO_MENU] if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] return [KAI_ACTIONS.INVALID] }, @@ -127,8 +127,8 @@ export const KAI_ACTIONS: ListActions = { type: ActionType.TEXT, }, COMING_SOON: { - title: 'πŸƒπŸ» Coming soon, do you want to go back to the main menu?', - type: ActionType.TEXT, + title: 'πŸƒπŸ» Coming soon ...', + type: ActionType.INVALID, }, INVALID: { title: '❌ Invalid input, please follow the instruction!', @@ -353,11 +353,11 @@ export const KAI_ACTIONS: ListActions = { }, SEE_MARKET_TRENDS: { type: ActionType.OPTION, - data: [KAI_OPTIONS.TOP_BIG_SPREAD, KAI_OPTIONS.TOP_GAINERS, KAI_OPTIONS.TOP_VOLUME, KAI_OPTIONS.BACK_TO_MENU], + data: [KAI_OPTIONS.TOP_GAINERS, KAI_OPTIONS.TOP_VOLUME, KAI_OPTIONS.TOP_BIG_SPREAD, KAI_OPTIONS.BACK_TO_MENU], response: ({ answer }: { answer: string }) => { if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] if (answer === KAI_OPTIONS.TOP_BIG_SPREAD.title.toLowerCase()) - return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] + return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.INVALID_BACK_TO_MENU] if ( answer === KAI_OPTIONS.TOP_GAINERS.title.toLowerCase() || answer === KAI_OPTIONS.TOP_VOLUME.title.toLowerCase() @@ -369,7 +369,7 @@ export const KAI_ACTIONS: ListActions = { }, SEE_MARKET_TRENDS_CHOOSE_AMOUNT: { type: ActionType.OPTION, - data: [10, 15, 20].map((item: number) => ({ title: item.toString(), space: Space.ONE_THIRD_WIDTH })), + data: [5, 10, 15].map((item: number) => ({ title: item.toString(), space: Space.ONE_THIRD_WIDTH })), response: async ({ answer, chainId, @@ -455,7 +455,7 @@ export const KAI_ACTIONS: ListActions = { response: ({ answer, quoteSymbol }: { answer: string; quoteSymbol: string }) => { if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] if (answer === KAI_OPTIONS.SWAP_TOKEN.title.toLowerCase()) - return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] + return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.INVALID_BACK_TO_MENU] const index = resultToActionData.findIndex((item: KaiOption) => item.title.toLowerCase() === answer) diff --git a/src/components/Kai/styled.tsx b/src/components/Kai/styled.tsx index 7f32623133..3e4b399353 100644 --- a/src/components/Kai/styled.tsx +++ b/src/components/Kai/styled.tsx @@ -167,6 +167,10 @@ export const Loader = styled.div` export const ChatPanel = styled.div` max-height: 360px; overflow: scroll; + + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + max-height: 50vh; + `} ` export const ActionPanel = styled.div` From 42c022452ed7e02e66a65892f837dfe3181aa228 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 20:23:57 +0700 Subject: [PATCH 13/28] Change panel index --- src/components/Kai/actions.ts | 6 ++++-- src/components/Kai/styled.tsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index eec39967fc..98a12edf45 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -457,9 +457,11 @@ export const KAI_ACTIONS: ListActions = { if (answer === KAI_OPTIONS.SWAP_TOKEN.title.toLowerCase()) return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.INVALID_BACK_TO_MENU] - const index = resultToActionData.findIndex((item: KaiOption) => item.title.toLowerCase() === answer) + const index = result.findIndex( + (item: any) => item.symbol.toLowerCase() === answer.split(' ')?.[1].toLowerCase(), + ) - if (index > 1) { + if (index > -1) { const token = result[index] return [ diff --git a/src/components/Kai/styled.tsx b/src/components/Kai/styled.tsx index 3e4b399353..b1cd7070bf 100644 --- a/src/components/Kai/styled.tsx +++ b/src/components/Kai/styled.tsx @@ -26,7 +26,7 @@ export const Modal = styled(motion.div)` position: fixed; bottom: 5.2rem; right: 1rem; - z-index: 1; + z-index: 10; font-size: 14px; width: fit-content; height: fit-content; From cf102f53d00455ecf62d698c1296e8c0a2488d63 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 21:30:19 +0700 Subject: [PATCH 14/28] Change chain selector UI --- src/components/Kai/KaiPanel.tsx | 70 +++++++++++++++++++-------------- src/components/Kai/actions.ts | 3 ++ src/components/Kai/styled.tsx | 61 +++++++++++++++++++++++++++- src/constants/networks/type.ts | 1 + 4 files changed, 103 insertions(+), 32 deletions(-) diff --git a/src/components/Kai/KaiPanel.tsx b/src/components/Kai/KaiPanel.tsx index 777fada444..42dca9a775 100644 --- a/src/components/Kai/KaiPanel.tsx +++ b/src/components/Kai/KaiPanel.tsx @@ -1,30 +1,37 @@ -import { rgba } from 'polished' +import { ChainId } from '@kyberswap/ks-sdk-core' import { ChangeEvent, KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react' import { Flex } from 'rebass' import { useGetQuoteByChainQuery } from 'services/marketOverview' import { ReactComponent as KaiAvatar } from 'assets/svg/kai_avatar.svg' -import { MouseoverTooltip } from 'components/Tooltip' +import NavGroup from 'components/Header/groups/NavGroup' +import { DropdownTextAnchor } from 'components/Header/styleds' import { MAINNET_NETWORKS } from 'constants/networks' import { useAllTokens } from 'hooks/Tokens' import { NETWORKS_INFO } from 'hooks/useChainsConfig' -import useTheme from 'hooks/useTheme' import { ActionType, KAI_ACTIONS, KaiAction, KaiOption } from './actions' import { ActionButton, ActionPanel, ActionText, + ChainAnchorBackground, + ChainAnchorWrapper, + ChainItem, + ChainSelectorWrapper, ChatInput, ChatPanel, ChatWrapper, Divider, + HeaderSubText, + HeaderTextName, + KaiHeaderLeft, KaiHeaderWrapper, Loader, LoadingWrapper, MainActionButton, + SelectedChainImg, SendIcon, - SubTextSpan, UserMessage, UserMessageWrapper, } from './styled' @@ -164,36 +171,39 @@ const KaiPanel = () => { ) } -const KaiHeader = ({ chainId, setChainId }: { chainId: number; setChainId: (value: number) => void }) => { - const theme = useTheme() - +const KaiHeader = ({ chainId, setChainId }: { chainId: ChainId; setChainId: (value: number) => void }) => { return ( <> - - I'm KAI - Kyber Assistant Interface + + + + I'm KAI + Kyber Assistant Interface + + + + + + + + + } + dropdownContent={ + + {MAINNET_NETWORKS.map(item => ( + setChainId(item)} active={item === chainId}> + + {NETWORKS_INFO[item].displayName} + + ))} + + } + /> - - {MAINNET_NETWORKS.map(item => ( - - setChainId(item)} - sx={{ - background: chainId === item ? rgba(theme.primary, 0.2) : undefined, - border: chainId === item ? `1px solid ${theme.primary}` : 'none', - borderRadius: '4px', - }} - style={{ cursor: 'pointer' }} - > - - - - ))} - ) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index 98a12edf45..60e4ec7c3b 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -381,6 +381,9 @@ export const KAI_ACTIONS: ListActions = { arg: any quoteSymbol: string }) => { + if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] + if (!['5', '10', '15'].includes(answer.toString())) return [KAI_ACTIONS.INVALID, KAI_ACTIONS.INVALID_BACK_TO_MENU] + const filter: any = { chainId: chainId, search: '', diff --git a/src/components/Kai/styled.tsx b/src/components/Kai/styled.tsx index b1cd7070bf..06371af889 100644 --- a/src/components/Kai/styled.tsx +++ b/src/components/Kai/styled.tsx @@ -50,12 +50,69 @@ export const ModalContent = styled.div` export const KaiHeaderWrapper = styled.div` display: flex; - flex-wrap: wrap; + align-items: center; + justify-content: space-between; +` + +export const KaiHeaderLeft = styled.div` + display: flex; gap: 6px; align-items: center; ` -export const SubTextSpan = styled.span` +export const ChainAnchorWrapper = styled.div` + position: relative; + display: flex; + align-items: center; + padding: 4px; +` + +export const ChainItem = styled.div<{ active: boolean }>` + display: flex; + gap: 12px; + align-items: center; + color: ${({ theme }) => theme.white}; + + ${({ theme, active }) => + active && + css` + color: ${theme.subText}; + `} +` + +export const SelectedChainImg = styled.img` + width: 18px; + height: 18px; + position: relative; + left: 6px; +` + +export const ChainAnchorBackground = styled.div` + position: absolute; + background-color: ${({ theme }) => rgba(theme.white, 0.1)}; + border-radius: 16px; + top: 0; + left: 4px; + width: 180%; + height: 100%; +` + +export const ChainSelectorWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 10px; + font-size: 14px; + max-height: 155px; + overflow: auto; + padding: 6px; +` + +export const HeaderTextName = styled.p` + margin: 0; +` + +export const HeaderSubText = styled.p` + margin: 0; color: ${({ theme }) => theme.subText}; ` diff --git a/src/constants/networks/type.ts b/src/constants/networks/type.ts index c346bef28f..b0bc023ffe 100644 --- a/src/constants/networks/type.ts +++ b/src/constants/networks/type.ts @@ -4,6 +4,7 @@ import { EnvKeys } from 'constants/env' import { ChainState } from 'hooks/useChainsConfig' export interface NetworkInfo { + readonly displayName?: string readonly chainId: ChainId // route can be used to detect which chain is favored in query param, check out useActiveNetwork.ts From ab8ffbf576bd908cee2a6ced69f71f0482c2ed75 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 22:36:03 +0700 Subject: [PATCH 15/28] Fix bug choose number of top market trends --- src/components/Kai/actions.ts | 2 +- src/components/Kai/styled.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index 60e4ec7c3b..d1c07ed45d 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -461,7 +461,7 @@ export const KAI_ACTIONS: ListActions = { return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.INVALID_BACK_TO_MENU] const index = result.findIndex( - (item: any) => item.symbol.toLowerCase() === answer.split(' ')?.[1].toLowerCase(), + (item: any) => item.symbol.toLowerCase() === answer.split(' ')?.[1]?.toLowerCase(), ) if (index > -1) { diff --git a/src/components/Kai/styled.tsx b/src/components/Kai/styled.tsx index 06371af889..a7503d5947 100644 --- a/src/components/Kai/styled.tsx +++ b/src/components/Kai/styled.tsx @@ -64,7 +64,7 @@ export const ChainAnchorWrapper = styled.div` position: relative; display: flex; align-items: center; - padding: 4px; + padding: 5px; ` export const ChainItem = styled.div<{ active: boolean }>` @@ -81,8 +81,8 @@ export const ChainItem = styled.div<{ active: boolean }>` ` export const SelectedChainImg = styled.img` - width: 18px; - height: 18px; + width: 20px; + height: 20px; position: relative; left: 6px; ` @@ -92,8 +92,8 @@ export const ChainAnchorBackground = styled.div` background-color: ${({ theme }) => rgba(theme.white, 0.1)}; border-radius: 16px; top: 0; - left: 4px; - width: 180%; + left: 3px; + width: 190%; height: 100%; ` From 485da4c675baef1ef76c93f8947e88d085640998 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 22:53:23 +0700 Subject: [PATCH 16/28] Fix to show symbol instead of address --- src/components/Kai/actions.ts | 2 +- src/components/Kai/styled.tsx | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index d1c07ed45d..48b10286b4 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -198,7 +198,7 @@ export const KAI_ACTIONS: ListActions = { return [ { - title: `Here’s what I’ve got for ${answer}`, + title: `Here’s what I’ve got for ${token.symbol}`, type: ActionType.TEXT, }, { diff --git a/src/components/Kai/styled.tsx b/src/components/Kai/styled.tsx index a7503d5947..228b169461 100644 --- a/src/components/Kai/styled.tsx +++ b/src/components/Kai/styled.tsx @@ -290,6 +290,8 @@ export const UserMessage = styled.p<{ havePrevious: boolean; haveFollowing: bool width: fit-content; border-radius: 16px; padding: 8px 12px; + max-width: 100%; + word-wrap: break-word; ${({ havePrevious, haveFollowing }) => css` From a1604f3fbcfd797b52ef84a6e962f3eee540a967 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 26 Sep 2024 23:03:28 +0700 Subject: [PATCH 17/28] Add action search another token --- src/components/Kai/actions.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index 48b10286b4..6204422332 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -72,6 +72,10 @@ const KAI_OPTIONS: ListOptions = { title: 'Top 24h Volume', space: Space.HALF_WIDTH, }, + SEARCH_ANOTHER_TOKEN: { + title: 'Search another token', + space: Space.FULL_WIDTH, + }, BACK_TO_MENU: { title: '↩ Back to the main menu', space: Space.FULL_WIDTH, @@ -114,11 +118,19 @@ export const KAI_ACTIONS: ListActions = { }, DO_SOMETHING_AFTER_CHECK_PRICE: { type: ActionType.OPTION, - data: [KAI_OPTIONS.SWAP_TOKEN, KAI_OPTIONS.BACK_TO_MENU], + data: [KAI_OPTIONS.SWAP_TOKEN, KAI_OPTIONS.SEARCH_ANOTHER_TOKEN, KAI_OPTIONS.BACK_TO_MENU], response: ({ answer }: { answer: string }) => { if (answer === KAI_OPTIONS.SWAP_TOKEN.title.toLowerCase()) return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.INVALID_BACK_TO_MENU] if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] + if (answer === KAI_OPTIONS.SEARCH_ANOTHER_TOKEN.title.toLowerCase()) + return [ + { + title: 'πŸ’ͺ🏼 Okay! Which token are you interested in? Just type the name or address.', + type: ActionType.TEXT, + response: KAI_ACTIONS.TYPE_TOKEN_TO_CHECK_PRICE.response, + }, + ] return [KAI_ACTIONS.INVALID] }, }, From e8999a93d455787feebc2d7abca9366777f5b6e4 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 27 Sep 2024 01:37:21 +0700 Subject: [PATCH 18/28] Add static flow for Swap token action --- src/components/Kai/actions.ts | 539 ++++++++++++++++++++++++++++++++++ src/components/Kai/utils.ts | 3 + 2 files changed, 542 insertions(+) create mode 100644 src/components/Kai/utils.ts diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index 6204422332..2fa04db15b 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -1,5 +1,7 @@ import { formatDisplayNumber } from 'utils/numbers' +import { isNumber } from './utils' + export enum Space { HALF_WIDTH = 'calc(50% - 6px)', FULL_WIDTH = '100%', @@ -76,10 +78,18 @@ const KAI_OPTIONS: ListOptions = { title: 'Search another token', space: Space.FULL_WIDTH, }, + CUSTOM_MAX_SLIPPAGE: { + title: 'Custom', + space: Space.ONE_THIRD_WIDTH, + }, BACK_TO_MENU: { title: '↩ Back to the main menu', space: Space.FULL_WIDTH, }, + CONFIRM_SWAP: { + title: 'Confirm trade', + space: Space.FULL_WIDTH, + }, } export const MAIN_MENU: KaiOption[] = [ @@ -99,6 +109,8 @@ export const KAI_ACTIONS: ListActions = { if (answer === KAI_OPTIONS.CHECK_TOKEN_PRICE.title.toLowerCase()) return [KAI_ACTIONS.TYPE_TOKEN_TO_CHECK_PRICE] if (answer === KAI_OPTIONS.SEE_MARKET_TRENDS.title.toLowerCase()) return [KAI_ACTIONS.SEE_MARKET_TRENDS_WELCOME, KAI_ACTIONS.SEE_MARKET_TRENDS] + if (answer === KAI_OPTIONS.SWAP_TOKEN.title.toLowerCase()) + return [KAI_ACTIONS.SWAP_TOKEN, KAI_ACTIONS.SWAP_INPUT_TOKEN_IN] if (MAIN_MENU.find((option: KaiOption) => answer.trim().toLowerCase() === option.title.toLowerCase())) return [KAI_ACTIONS.COMING_SOON] return [KAI_ACTIONS.INVALID] @@ -205,6 +217,15 @@ export const KAI_ACTIONS: ListActions = { .filter((token: any) => token.token) .sort((a: any, b: any) => b.marketCap - a.marketCap) + // const result = data.assets + // .filter((token: any) => token.marketCap) + // .map((token: any) => ({ + // ...token, + // token: token.tokens.find((item: any) => item.chainId === chainId.toString()), + // })) + // .filter((token: any) => token.token) + // .sort((a: any, b: any) => b.marketCap - a.marketCap) + if (result.length === 1) { const token = result[0] @@ -547,4 +568,522 @@ export const KAI_ACTIONS: ListActions = { } }, }, + SWAP_TOKEN: { + title: 'πŸ’° Ready to trade! What are you swapping?', + type: ActionType.TEXT, + }, + SWAP_INPUT_TOKEN_IN: { + title: 'πŸ‘‰ Enter the token in you want to swap', + type: ActionType.TEXT, + placeholder: 'Enter the token in', + response: async ({ + answer, + chainId, + whitelistTokenAddress, + quoteSymbol, + }: { + answer: string + chainId: number + whitelistTokenAddress: string[] + quoteSymbol: string + }) => { + const filter: any = { + chainId: chainId, + search: answer, + page: 1, + pageSize: 50, + chainIds: chainId, + sort: '', + } + + try { + const res = await fetch( + `${import.meta.env.VITE_TOKEN_API_URL}/v1/public/assets?` + new URLSearchParams(filter).toString(), + { + method: 'GET', + }, + ) + const { data } = await res.json() + const result = data.assets + .filter( + (token: any) => + token.marketCap && token.tokens.find((item: any) => whitelistTokenAddress.includes(item.address)), + ) + .map((token: any) => ({ + ...token, + token: token.tokens.find( + (item: any) => item.chainId === chainId.toString() && whitelistTokenAddress.includes(item.address), + ), + })) + .filter((token: any) => token.token) + .sort((a: any, b: any) => b.marketCap - a.marketCap) + + if (result.length === 1) { + const token = result[0] + + return [ + { + type: ActionType.HTML, + title: ` +
πŸ“ˆ Buy Price: ${ + token.token.priceBuy + ? `${formatDisplayNumber(token.token.priceBuy, { + fractionDigits: 2, + significantDigits: 7, + })} ${quoteSymbol}` + : '--' + }
+
πŸ“ˆ Sell Price: ${ + token.token.priceSell + ? `${formatDisplayNumber(token.token.priceSell, { + fractionDigits: 2, + significantDigits: 7, + })} ${quoteSymbol}` + : '--' + }
+
πŸ”„ 24h Buy Price Change: ${ + token.token.priceBuyChange24h + ? `${token.token.priceBuyChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(token.token.priceBuyChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + }
+
πŸ”„ 24h Sell Price Change: ${ + token.token.priceSellChange24h + ? `${token.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(token.token.priceSellChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + }
+
πŸ’Έ 24h Volume: ${ + token.volume24h + ? formatDisplayNumber(token.volume24h, { style: 'currency', fractionDigits: 2 }) + : '--' + }
+
🏦 Market Cap: ${ + token.marketCap + ? formatDisplayNumber(token.marketCap, { style: 'currency', fractionDigits: 2 }) + : '--' + }
+ `, + }, + { + ...KAI_ACTIONS.SWAP_INPUT_AMOUNT_IN, + arg: { + tokenIn: token, + }, + }, + ] + } else if (result.length > 1) { + return [ + KAI_ACTIONS.TOKEN_FOUND, + { + type: ActionType.OPTION, + data: result + .map((item: any) => ({ + title: item.symbol, + space: item.symbol.length <= 10 ? Space.HALF_WIDTH : Space.FULL_WIDTH, + })) + .concat(KAI_ACTIONS.INVALID_BACK_TO_MENU.data), + response: ({ answer: tokenSymbolSelected }: { answer: string }) => { + if (tokenSymbolSelected === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] + + const token = result.find((item: any) => item.symbol.toLowerCase() === tokenSymbolSelected) + if (token) + return [ + { + type: ActionType.HTML, + title: ` +
πŸ“ˆ Buy Price: ${ + token.token.priceBuy + ? `${formatDisplayNumber(token.token.priceBuy, { + fractionDigits: 2, + significantDigits: 7, + })} ${quoteSymbol}` + : '--' + }
+
πŸ“ˆ Sell Price: ${ + token.token.priceSell + ? `${formatDisplayNumber(token.token.priceSell, { + fractionDigits: 2, + significantDigits: 7, + })} ${quoteSymbol}` + : '--' + }
+
πŸ”„ 24h Buy Price Change: ${ + token.token.priceBuyChange24h + ? `${token.token.priceBuyChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(token.token.priceBuyChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + }
+
πŸ”„ 24h Sell Price Change: ${ + token.token.priceSellChange24h + ? `${token.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(token.token.priceSellChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + }
+
πŸ’Έ 24h Volume: ${ + token.volume24h + ? formatDisplayNumber(token.volume24h, { style: 'currency', fractionDigits: 2 }) + : '--' + }
+
🏦 Market Cap: ${ + token.marketCap + ? formatDisplayNumber(token.marketCap, { style: 'currency', fractionDigits: 2 }) + : '--' + }
+ `, + }, + { + ...KAI_ACTIONS.SWAP_INPUT_AMOUNT_IN, + arg: { + tokenIn: token, + }, + }, + ] + + return [KAI_ACTIONS.TOKEN_NOT_FOUND, KAI_ACTIONS.INVALID_BACK_TO_MENU] + }, + }, + ] + } + + return [KAI_ACTIONS.TOKEN_NOT_FOUND, KAI_ACTIONS.INVALID_BACK_TO_MENU] + } catch (error) { + return [KAI_ACTIONS.ERROR, KAI_ACTIONS.INVALID_BACK_TO_MENU] + } + }, + }, + SWAP_INPUT_AMOUNT_IN: { + title: 'πŸ‘‰ Enter the amount in you want to swap', + type: ActionType.TEXT, + placeholder: 'Enter the amount in', + response: ({ answer, arg }: { answer: string; arg: any }) => { + if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] + if (!isNumber(answer)) return [KAI_ACTIONS.INVALID, KAI_ACTIONS.INVALID_BACK_TO_MENU] + + return [ + { + ...KAI_ACTIONS.SWAP_INPUT_TOKEN_OUT, + arg: { + ...arg, + amountIn: answer, + }, + }, + ] + }, + }, + SWAP_INPUT_TOKEN_OUT: { + title: 'πŸ‘‰ Enter the token out you want to swap', + type: ActionType.TEXT, + placeholder: 'Enter the token out', + response: async ({ + answer, + chainId, + whitelistTokenAddress, + arg, + quoteSymbol, + }: { + answer: string + chainId: number + whitelistTokenAddress: string[] + arg: any + quoteSymbol: string + }) => { + const filter: any = { + chainId: chainId, + search: answer, + page: 1, + pageSize: 50, + chainIds: chainId, + sort: '', + } + + try { + const res = await fetch( + `${import.meta.env.VITE_TOKEN_API_URL}/v1/public/assets?` + new URLSearchParams(filter).toString(), + { + method: 'GET', + }, + ) + const { data } = await res.json() + const result = data.assets + .filter( + (token: any) => + token.marketCap && token.tokens.find((item: any) => whitelistTokenAddress.includes(item.address)), + ) + .map((token: any) => ({ + ...token, + token: token.tokens.find( + (item: any) => item.chainId === chainId.toString() && whitelistTokenAddress.includes(item.address), + ), + })) + .filter((token: any) => token.token) + .sort((a: any, b: any) => b.marketCap - a.marketCap) + + if (result.length === 1) { + const token = result[0] + + return [ + { + type: ActionType.HTML, + title: ` +
πŸ“ˆ Buy Price: ${ + token.token.priceBuy + ? `${formatDisplayNumber(token.token.priceBuy, { + fractionDigits: 2, + significantDigits: 7, + })} ${quoteSymbol}` + : '--' + }
+
πŸ“ˆ Sell Price: ${ + token.token.priceSell + ? `${formatDisplayNumber(token.token.priceSell, { + fractionDigits: 2, + significantDigits: 7, + })} ${quoteSymbol}` + : '--' + }
+
πŸ”„ 24h Buy Price Change: ${ + token.token.priceBuyChange24h + ? `${token.token.priceBuyChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(token.token.priceBuyChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + }
+
πŸ”„ 24h Sell Price Change: ${ + token.token.priceSellChange24h + ? `${token.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(token.token.priceSellChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + }
+
πŸ’Έ 24h Volume: ${ + token.volume24h + ? formatDisplayNumber(token.volume24h, { style: 'currency', fractionDigits: 2 }) + : '--' + }
+
🏦 Market Cap: ${ + token.marketCap + ? formatDisplayNumber(token.marketCap, { style: 'currency', fractionDigits: 2 }) + : '--' + }
+ `, + }, + KAI_ACTIONS.SWAP_INPUT_SLIPPAGE_TEXT, + { + ...KAI_ACTIONS.SWAP_INPUT_SLIPPAGE, + arg: { + ...arg, + tokenOut: token, + }, + }, + ] + } else if (result.length > 1) { + return [ + KAI_ACTIONS.TOKEN_FOUND, + { + type: ActionType.OPTION, + data: result + .map((item: any) => ({ + title: item.symbol, + space: item.symbol.length <= 10 ? Space.HALF_WIDTH : Space.FULL_WIDTH, + })) + .concat(KAI_ACTIONS.INVALID_BACK_TO_MENU.data), + response: ({ answer: tokenSymbolSelected }: { answer: string }) => { + if (tokenSymbolSelected === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] + + const token = result.find((item: any) => item.symbol.toLowerCase() === tokenSymbolSelected) + if (token) + return [ + { + type: ActionType.HTML, + title: ` +
πŸ“ˆ Buy Price: ${ + token.token.priceBuy + ? `${formatDisplayNumber(token.token.priceBuy, { + fractionDigits: 2, + significantDigits: 7, + })} ${quoteSymbol}` + : '--' + }
+
πŸ“ˆ Sell Price: ${ + token.token.priceSell + ? `${formatDisplayNumber(token.token.priceSell, { + fractionDigits: 2, + significantDigits: 7, + })} ${quoteSymbol}` + : '--' + }
+
πŸ”„ 24h Buy Price Change: ${ + token.token.priceBuyChange24h + ? `${token.token.priceBuyChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(token.token.priceBuyChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + }
+
πŸ”„ 24h Sell Price Change: ${ + token.token.priceSellChange24h + ? `${token.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( + Math.abs(token.token.priceSellChange24h), + { + style: 'decimal', + fractionDigits: 2, + }, + )}%` + : '--' + }
+
πŸ’Έ 24h Volume: ${ + token.volume24h + ? formatDisplayNumber(token.volume24h, { style: 'currency', fractionDigits: 2 }) + : '--' + }
+
🏦 Market Cap: ${ + token.marketCap + ? formatDisplayNumber(token.marketCap, { style: 'currency', fractionDigits: 2 }) + : '--' + }
+ `, + }, + KAI_ACTIONS.SWAP_INPUT_SLIPPAGE_TEXT, + { + ...KAI_ACTIONS.SWAP_INPUT_SLIPPAGE, + arg: { + ...arg, + tokenOut: token, + }, + }, + ] + + return [KAI_ACTIONS.TOKEN_NOT_FOUND, KAI_ACTIONS.INVALID_BACK_TO_MENU] + }, + }, + ] + } + + return [KAI_ACTIONS.TOKEN_NOT_FOUND, KAI_ACTIONS.INVALID_BACK_TO_MENU] + } catch (error) { + return [KAI_ACTIONS.ERROR, KAI_ACTIONS.INVALID_BACK_TO_MENU] + } + }, + }, + SWAP_INPUT_SLIPPAGE_TEXT: { + title: 'πŸ‘‰ Choose the max slippage you can accept', + type: ActionType.TEXT, + }, + SWAP_INPUT_SLIPPAGE: { + type: ActionType.OPTION, + data: [0.1, 0.5, 1, 5, 10] + .map(item => ({ title: `${item} %`, space: Space.ONE_THIRD_WIDTH })) + .concat([KAI_OPTIONS.CUSTOM_MAX_SLIPPAGE]), + response: ({ answer, arg, quoteSymbol }: { answer: string; arg: any; quoteSymbol: string }) => { + if (answer === KAI_OPTIONS.CUSTOM_MAX_SLIPPAGE.title.toLowerCase()) + return [{ ...KAI_ACTIONS.CUSTOM_MAX_SLIPPAGE, arg }] + + if (['0.1 %', '0.5 %', '1 %', '5 %', '10 %'].includes(answer)) { + const slippage = answer.replace('%', '') + return [ + { + type: ActionType.HTML, + title: ` +
πŸ”„ You're swapping: ${arg.amountIn} of ${arg.tokenIn.symbol}, est. ${quoteSymbol} value ${ + arg.tokenIn.token.priceSell * arg.amountIn + }
+
πŸ’° For: ${(arg.amountIn * arg.tokenIn.token.priceSell) / arg.tokenIn.token.priceBuy} of ${ + arg.tokenOut.symbol + }, est. ${quoteSymbol} value ${arg.amountIn * arg.tokenIn.token.priceSell}
+
βš–οΈ Slippage tolerance: ${slippage}%
+
πŸ“ Min receive: [MinAmountout]
+
πŸ“‰ Price Impact: [Price Impact]
+ `, + }, + KAI_ACTIONS.CONFIRM_SWAP_TOKEN_TEXT, + { + ...KAI_ACTIONS.CONFIRM_SWAP_TOKEN, + arg: { ...arg, slippage }, + }, + ] + } + + return [KAI_ACTIONS.INVALID, KAI_ACTIONS.INVALID_BACK_TO_MENU] + }, + }, + CUSTOM_MAX_SLIPPAGE: { + title: 'πŸ‘‰ Enter the max slippage you can accept (in percent)', + type: ActionType.TEXT, + placeholder: 'Enter the max slippage', + response: ({ answer, arg, quoteSymbol }: { answer: string; arg: any; quoteSymbol: string }) => { + if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] + if (!isNumber(answer)) return [KAI_ACTIONS.INVALID, KAI_ACTIONS.INVALID_BACK_TO_MENU] + + return [ + { + type: ActionType.HTML, + title: ` +
πŸ”„ You're awapping: ${arg.amountIn} of ${arg.tokenIn.symbol}, est. ${quoteSymbol} value ${ + arg.tokenIn.token.priceSell * arg.amountIn + }
+
πŸ’° For: ${(arg.amountIn * arg.tokenIn.token.priceSell) / arg.tokenOut.token.priceBuy} of ${ + arg.tokenOut.symbol + }, est. ${quoteSymbol} value ${arg.amountIn * arg.tokenIn.token.priceSell}
+
βš–οΈ Slippage tolerance: ${answer}%
+
πŸ“ Min receive: [MinAmountout]
+
πŸ“‰ Price Impact: [Price Impact]
+ `, + }, + KAI_ACTIONS.CONFIRM_SWAP_TOKEN_TEXT, + { + ...KAI_ACTIONS.CONFIRM_SWAP_TOKEN, + arg: { + ...arg, + slippage: answer, + }, + }, + ] + }, + }, + CONFIRM_SWAP_TOKEN_TEXT: { + title: 'Ready to execute the trade❓', + type: ActionType.TEXT, + }, + CONFIRM_SWAP_TOKEN: { + type: ActionType.OPTION, + data: [KAI_OPTIONS.CONFIRM_SWAP, KAI_OPTIONS.BACK_TO_MENU], + response: ({ answer, arg }: { answer: string; arg: any }) => { + console.log(arg) + if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] + + return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.BACK_TO_MENU] + }, + }, } diff --git a/src/components/Kai/utils.ts b/src/components/Kai/utils.ts new file mode 100644 index 0000000000..44733192a7 --- /dev/null +++ b/src/components/Kai/utils.ts @@ -0,0 +1,3 @@ +export const isNumber = (input: any) => { + return !/\D/.test(input) +} From 95153e8c6b3f80449dd2e12817f7c7d8e3889c9f Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 27 Sep 2024 09:04:26 +0700 Subject: [PATCH 19/28] Disabled previous actions --- package.json | 1 + src/components/Kai/KaiPanel.tsx | 32 ++++++++++++++++++++++++-------- src/components/Kai/actions.ts | 3 +++ src/components/Kai/styled.tsx | 20 +++++++++++++++++--- yarn.lock | 5 +++++ 5 files changed, 50 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 039885d022..f901885e9f 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "swiper": "^8.4.4", "ua-parser-js": "^1.0.33", "util": "^0.12.5", + "uuid": "^10.0.0", "viem": "^2.20.0", "vite-plugin-env-compatible": "^1.1.1", "wagmi": "^2.12.7", diff --git a/src/components/Kai/KaiPanel.tsx b/src/components/Kai/KaiPanel.tsx index 42dca9a775..f1362d8934 100644 --- a/src/components/Kai/KaiPanel.tsx +++ b/src/components/Kai/KaiPanel.tsx @@ -2,6 +2,7 @@ import { ChainId } from '@kyberswap/ks-sdk-core' import { ChangeEvent, KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react' import { Flex } from 'rebass' import { useGetQuoteByChainQuery } from 'services/marketOverview' +import { v4 as uuidv4 } from 'uuid' import { ReactComponent as KaiAvatar } from 'assets/svg/kai_avatar.svg' import NavGroup from 'components/Header/groups/NavGroup' @@ -46,7 +47,9 @@ const KaiPanel = () => { const [chatPlaceHolderText, setChatPlaceHolderText] = useState(DEFAULT_CHAT_PLACEHOLDER_TEXT) const [loading, setLoading] = useState(false) const [loadingText, setLoadingText] = useState(DEFAULT_LOADING_TEXT) - const [listActions, setListActions] = useState([KAI_ACTIONS.MAIN_MENU]) + const [listActions, setListActions] = useState( + [KAI_ACTIONS.MAIN_MENU].map(item => ({ ...item, uuid: uuidv4() })), + ) const [chainId, setChainId] = useState(DEFAULT_CHAIN_ID) const whitelistTokens = useAllTokens(true, chainId) @@ -84,7 +87,8 @@ const KaiPanel = () => { const onChangeListActions = (newActions: KaiAction[]) => { const cloneListActions = [...listActions] - setListActions(cloneListActions.concat(newActions)) + const newActionsWithUuid = newActions.map(item => ({ ...item, uuid: uuidv4() })) + setListActions(cloneListActions.concat(newActionsWithUuid)) } const getActionResponse = async () => { @@ -125,11 +129,18 @@ const KaiPanel = () => {
GM! What can I do for you today? πŸ‘‹
- {listActions.map((action: KaiAction, index: number) => - action.type === ActionType.MAIN_OPTION ? ( + {listActions.map((action: KaiAction, index: number) => { + const disabled = !action.uuid || !lastAction?.uuid || action.uuid !== lastAction.uuid + + return action.type === ActionType.MAIN_OPTION ? ( {action.data?.map((option: KaiOption, optionIndex: number) => ( - onSubmitChat(option.title)}> + !disabled && onSubmitChat(option.title)} + > {option.title} ))} @@ -137,7 +148,12 @@ const KaiPanel = () => { ) : action.type === ActionType.OPTION || action.type === ActionType.INVALID_AND_BACK ? ( {action.data?.map((option: KaiOption, optionIndex: number) => ( - onSubmitChat(option.title)}> + !disabled && onSubmitChat(option.title)} + > {option.title} ))} @@ -157,8 +173,8 @@ const KaiPanel = () => { {action.title} - ) : null, - )} + ) : null + })}
{loading && } diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index 2fa04db15b..758bd189b6 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -19,6 +19,7 @@ export enum ActionType { } export interface KaiAction { + uuid?: string title?: string type: ActionType data?: KaiOption[] @@ -587,6 +588,7 @@ export const KAI_ACTIONS: ListActions = { whitelistTokenAddress: string[] quoteSymbol: string }) => { + if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] const filter: any = { chainId: chainId, search: answer, @@ -808,6 +810,7 @@ export const KAI_ACTIONS: ListActions = { arg: any quoteSymbol: string }) => { + if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] const filter: any = { chainId: chainId, search: answer, diff --git a/src/components/Kai/styled.tsx b/src/components/Kai/styled.tsx index 228b169461..e310c7dc92 100644 --- a/src/components/Kai/styled.tsx +++ b/src/components/Kai/styled.tsx @@ -238,7 +238,7 @@ export const ActionPanel = styled.div` margin-top: 16px; ` -export const ActionButton = styled.div<{ width: string }>` +export const ActionButton = styled.div<{ width: string; disabled: boolean }>` display: flex; align-items: center; justify-content: center; @@ -253,18 +253,32 @@ export const ActionButton = styled.div<{ width: string }>` background-color: ${({ theme }) => rgba(theme.white, 0.08)}; } - ${({ width }) => + ${({ width, disabled, theme }) => css` width: ${width}; + ${disabled && + ` + background-color: ${rgba(theme.disableText, 0.4)} !important; + cursor: not-allowed; + `} `} ` -export const MainActionButton = styled(ActionButton)` +export const MainActionButton = styled(ActionButton)<{ disabled: boolean }>` background-color: ${({ theme }) => rgba(theme.primary, 0.1)}; :hover { background-color: ${({ theme }) => rgba(theme.primary, 0.18)}; } + + ${({ disabled, theme }) => + css` + ${disabled && + ` + background-color: ${rgba(theme.disableText, 0.4)} !important; + cursor: not-allowed; + `} + `} ` export const ActionText = styled.div` diff --git a/yarn.lock b/yarn.lock index 6d1abb2957..697798ac2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19406,6 +19406,11 @@ utrie@^1.0.2: dependencies: base64-arraybuffer "^1.0.2" +uuid@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" + integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== + uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" From 983825fde7d57006ebdf9bbfecd0109981e81fd5 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 27 Sep 2024 10:01:23 +0700 Subject: [PATCH 20/28] Change some Kai answer styles and re-sort MAIN MENU order --- src/components/Kai/actions.ts | 232 ++++++---------------------------- 1 file changed, 42 insertions(+), 190 deletions(-) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index 758bd189b6..a7449dbf25 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -96,8 +96,8 @@ const KAI_OPTIONS: ListOptions = { export const MAIN_MENU: KaiOption[] = [ KAI_OPTIONS.CHECK_TOKEN_PRICE, KAI_OPTIONS.SEE_MARKET_TRENDS, - KAI_OPTIONS.FIND_HIGH_APY_POOLS, KAI_OPTIONS.SWAP_TOKEN, + KAI_OPTIONS.FIND_HIGH_APY_POOLS, KAI_OPTIONS.ADD_LIQUIDITY, ] @@ -238,7 +238,7 @@ export const KAI_ACTIONS: ListActions = { { type: ActionType.HTML, title: ` -
πŸ“ˆ Buy Price: ${ +
πŸ“ˆ Buy Price: ${ token.token.priceBuy ? `${formatDisplayNumber(token.token.priceBuy, { fractionDigits: 2, @@ -246,7 +246,7 @@ export const KAI_ACTIONS: ListActions = { })} ${quoteSymbol}` : '--' }
-
πŸ“ˆ Sell Price: ${ +
πŸ“ˆ Sell Price: ${ token.token.priceSell ? `${formatDisplayNumber(token.token.priceSell, { fractionDigits: 2, @@ -254,7 +254,7 @@ export const KAI_ACTIONS: ListActions = { })} ${quoteSymbol}` : '--' }
-
πŸ”„ 24h Buy Price Change: ${ +
πŸ”„ 24h Buy Price Change: ${ token.token.priceBuyChange24h ? `${token.token.priceBuyChange24h < 0 ? '-' : ''}${formatDisplayNumber( Math.abs(token.token.priceBuyChange24h), @@ -265,7 +265,7 @@ export const KAI_ACTIONS: ListActions = { )}%` : '--' }
-
πŸ”„ 24h Sell Price Change: ${ +
πŸ”„ 24h Sell Price Change: ${ token.token.priceSellChange24h ? `${token.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( Math.abs(token.token.priceSellChange24h), @@ -276,7 +276,7 @@ export const KAI_ACTIONS: ListActions = { )}%` : '--' }
-
πŸ’Έ 24h Volume: ${ +
πŸ’Έ 24h Volume: ${ token.volume24h ? formatDisplayNumber(token.volume24h, { style: 'currency', fractionDigits: 2 }) : '--' @@ -315,7 +315,7 @@ export const KAI_ACTIONS: ListActions = { { type: ActionType.HTML, title: ` -
πŸ“ˆ Buy Price: ${ +
πŸ“ˆ Buy Price: ${ token.token.priceBuy ? `${formatDisplayNumber(token.token.priceBuy, { fractionDigits: 2, @@ -323,7 +323,7 @@ export const KAI_ACTIONS: ListActions = { })} ${quoteSymbol}` : '--' }
-
πŸ“ˆ Sell Price: ${ +
πŸ“ˆ Sell Price: ${ token.token.priceSell ? `${formatDisplayNumber(token.token.priceSell, { fractionDigits: 2, @@ -331,7 +331,7 @@ export const KAI_ACTIONS: ListActions = { })} ${quoteSymbol}` : '--' }
-
πŸ”„ 24h Buy Price Change: ${ +
πŸ”„ 24h Buy Price Change: ${ token.token.priceBuyChange24h ? `${token.token.priceBuyChange24h < 0 ? '-' : ''}${formatDisplayNumber( Math.abs(token.token.priceBuyChange24h), @@ -342,7 +342,7 @@ export const KAI_ACTIONS: ListActions = { )}%` : '--' }
-
πŸ”„ 24h Sell Price Change: ${ +
πŸ”„ 24h Sell Price Change: ${ token.token.priceSellChange24h ? `${token.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( Math.abs(token.token.priceSellChange24h), @@ -353,7 +353,7 @@ export const KAI_ACTIONS: ListActions = { )}%` : '--' }
-
πŸ’Έ 24h Volume: ${ +
πŸ’Έ 24h Volume: ${ token.volume24h ? formatDisplayNumber(token.volume24h, { style: 'currency', fractionDigits: 2 }) : '--' @@ -505,7 +505,7 @@ export const KAI_ACTIONS: ListActions = { { type: ActionType.HTML, title: ` -
πŸ“ˆ Buy Price: ${ +
πŸ“ˆ Buy Price: ${ token.token.priceBuy ? `${formatDisplayNumber(token.token.priceBuy, { fractionDigits: 2, @@ -513,7 +513,7 @@ export const KAI_ACTIONS: ListActions = { })} ${quoteSymbol}` : '--' }
-
πŸ“ˆ Sell Price: ${ +
πŸ“ˆ Sell Price: ${ token.token.priceSell ? formatDisplayNumber(token.token.priceSell, { fractionDigits: 2, @@ -521,7 +521,7 @@ export const KAI_ACTIONS: ListActions = { }) : '--' }
-
πŸ”„ 24h Buy Price Change: ${ +
πŸ”„ 24h Buy Price Change: ${ token.token.priceBuyChange24h ? `${token.token.priceBuyChange24h < 0 ? '-' : ''}${formatDisplayNumber( Math.abs(token.token.priceBuyChange24h), @@ -532,7 +532,7 @@ export const KAI_ACTIONS: ListActions = { )}%` : '--' }
-
πŸ”„ 24h Sell Price Change: ${ +
πŸ”„ 24h Sell Price Change: ${ token.token.priceSellChange24h ? `${token.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( Math.abs(token.token.priceSellChange24h), @@ -543,7 +543,7 @@ export const KAI_ACTIONS: ListActions = { )}%` : '--' }
-
πŸ’Έ 24h Volume: ${ +
πŸ’Έ 24h Volume: ${ token.volume24h ? formatDisplayNumber(token.volume24h, { style: 'currency', fractionDigits: 2 }) : '--' @@ -627,13 +627,8 @@ export const KAI_ACTIONS: ListActions = { { type: ActionType.HTML, title: ` -
πŸ“ˆ Buy Price: ${ - token.token.priceBuy - ? `${formatDisplayNumber(token.token.priceBuy, { - fractionDigits: 2, - significantDigits: 7, - })} ${quoteSymbol}` - : '--' +
πŸ“Œ Token contract: ${ + token.token.address }
πŸ“ˆ Sell Price: ${ token.token.priceSell @@ -643,38 +638,6 @@ export const KAI_ACTIONS: ListActions = { })} ${quoteSymbol}` : '--' }
-
πŸ”„ 24h Buy Price Change: ${ - token.token.priceBuyChange24h - ? `${token.token.priceBuyChange24h < 0 ? '-' : ''}${formatDisplayNumber( - Math.abs(token.token.priceBuyChange24h), - { - style: 'decimal', - fractionDigits: 2, - }, - )}%` - : '--' - }
-
πŸ”„ 24h Sell Price Change: ${ - token.token.priceSellChange24h - ? `${token.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( - Math.abs(token.token.priceSellChange24h), - { - style: 'decimal', - fractionDigits: 2, - }, - )}%` - : '--' - }
-
πŸ’Έ 24h Volume: ${ - token.volume24h - ? formatDisplayNumber(token.volume24h, { style: 'currency', fractionDigits: 2 }) - : '--' - }
-
🏦 Market Cap: ${ - token.marketCap - ? formatDisplayNumber(token.marketCap, { style: 'currency', fractionDigits: 2 }) - : '--' - }
`, }, { @@ -704,13 +667,8 @@ export const KAI_ACTIONS: ListActions = { { type: ActionType.HTML, title: ` -
πŸ“ˆ Buy Price: ${ - token.token.priceBuy - ? `${formatDisplayNumber(token.token.priceBuy, { - fractionDigits: 2, - significantDigits: 7, - })} ${quoteSymbol}` - : '--' +
πŸ“Œ Token contract: ${ + token.token.address }
πŸ“ˆ Sell Price: ${ token.token.priceSell @@ -720,38 +678,6 @@ export const KAI_ACTIONS: ListActions = { })} ${quoteSymbol}` : '--' }
-
πŸ”„ 24h Buy Price Change: ${ - token.token.priceBuyChange24h - ? `${token.token.priceBuyChange24h < 0 ? '-' : ''}${formatDisplayNumber( - Math.abs(token.token.priceBuyChange24h), - { - style: 'decimal', - fractionDigits: 2, - }, - )}%` - : '--' - }
-
πŸ”„ 24h Sell Price Change: ${ - token.token.priceSellChange24h - ? `${token.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( - Math.abs(token.token.priceSellChange24h), - { - style: 'decimal', - fractionDigits: 2, - }, - )}%` - : '--' - }
-
πŸ’Έ 24h Volume: ${ - token.volume24h - ? formatDisplayNumber(token.volume24h, { style: 'currency', fractionDigits: 2 }) - : '--' - }
-
🏦 Market Cap: ${ - token.marketCap - ? formatDisplayNumber(token.marketCap, { style: 'currency', fractionDigits: 2 }) - : '--' - }
`, }, { @@ -849,6 +775,9 @@ export const KAI_ACTIONS: ListActions = { { type: ActionType.HTML, title: ` +
πŸ“Œ Token contract: ${ + token.token.address + }
πŸ“ˆ Buy Price: ${ token.token.priceBuy ? `${formatDisplayNumber(token.token.priceBuy, { @@ -857,46 +786,6 @@ export const KAI_ACTIONS: ListActions = { })} ${quoteSymbol}` : '--' }
-
πŸ“ˆ Sell Price: ${ - token.token.priceSell - ? `${formatDisplayNumber(token.token.priceSell, { - fractionDigits: 2, - significantDigits: 7, - })} ${quoteSymbol}` - : '--' - }
-
πŸ”„ 24h Buy Price Change: ${ - token.token.priceBuyChange24h - ? `${token.token.priceBuyChange24h < 0 ? '-' : ''}${formatDisplayNumber( - Math.abs(token.token.priceBuyChange24h), - { - style: 'decimal', - fractionDigits: 2, - }, - )}%` - : '--' - }
-
πŸ”„ 24h Sell Price Change: ${ - token.token.priceSellChange24h - ? `${token.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( - Math.abs(token.token.priceSellChange24h), - { - style: 'decimal', - fractionDigits: 2, - }, - )}%` - : '--' - }
-
πŸ’Έ 24h Volume: ${ - token.volume24h - ? formatDisplayNumber(token.volume24h, { style: 'currency', fractionDigits: 2 }) - : '--' - }
-
🏦 Market Cap: ${ - token.marketCap - ? formatDisplayNumber(token.marketCap, { style: 'currency', fractionDigits: 2 }) - : '--' - }
`, }, KAI_ACTIONS.SWAP_INPUT_SLIPPAGE_TEXT, @@ -928,6 +817,9 @@ export const KAI_ACTIONS: ListActions = { { type: ActionType.HTML, title: ` +
πŸ“Œ Token contract: ${ + token.token.address + }
πŸ“ˆ Buy Price: ${ token.token.priceBuy ? `${formatDisplayNumber(token.token.priceBuy, { @@ -936,46 +828,6 @@ export const KAI_ACTIONS: ListActions = { })} ${quoteSymbol}` : '--' }
-
πŸ“ˆ Sell Price: ${ - token.token.priceSell - ? `${formatDisplayNumber(token.token.priceSell, { - fractionDigits: 2, - significantDigits: 7, - })} ${quoteSymbol}` - : '--' - }
-
πŸ”„ 24h Buy Price Change: ${ - token.token.priceBuyChange24h - ? `${token.token.priceBuyChange24h < 0 ? '-' : ''}${formatDisplayNumber( - Math.abs(token.token.priceBuyChange24h), - { - style: 'decimal', - fractionDigits: 2, - }, - )}%` - : '--' - }
-
πŸ”„ 24h Sell Price Change: ${ - token.token.priceSellChange24h - ? `${token.token.priceSellChange24h < 0 ? '-' : ''}${formatDisplayNumber( - Math.abs(token.token.priceSellChange24h), - { - style: 'decimal', - fractionDigits: 2, - }, - )}%` - : '--' - }
-
πŸ’Έ 24h Volume: ${ - token.volume24h - ? formatDisplayNumber(token.volume24h, { style: 'currency', fractionDigits: 2 }) - : '--' - }
-
🏦 Market Cap: ${ - token.marketCap - ? formatDisplayNumber(token.marketCap, { style: 'currency', fractionDigits: 2 }) - : '--' - }
`, }, KAI_ACTIONS.SWAP_INPUT_SLIPPAGE_TEXT, @@ -1019,14 +871,14 @@ export const KAI_ACTIONS: ListActions = { { type: ActionType.HTML, title: ` -
πŸ”„ You're swapping: ${arg.amountIn} of ${arg.tokenIn.symbol}, est. ${quoteSymbol} value ${ - arg.tokenIn.token.priceSell * arg.amountIn - }
-
πŸ’° For: ${(arg.amountIn * arg.tokenIn.token.priceSell) / arg.tokenIn.token.priceBuy} of ${ - arg.tokenOut.symbol - }, est. ${quoteSymbol} value ${arg.amountIn * arg.tokenIn.token.priceSell}
-
βš–οΈ Slippage tolerance: ${slippage}%
-
πŸ“ Min receive: [MinAmountout]
+
πŸ”„ You're swapping: ${arg.amountIn} of ${ + arg.tokenIn.symbol + }, est. ${quoteSymbol} value ${arg.tokenIn.token.priceSell * arg.amountIn}
+
πŸ’° For: ${ + (arg.amountIn * arg.tokenIn.token.priceSell) / arg.tokenIn.token.priceBuy + } of ${arg.tokenOut.symbol}, est. ${quoteSymbol} value ${arg.amountIn * arg.tokenIn.token.priceSell}
+
βš–οΈ Slippage tolerance: ${slippage}%
+
πŸ“ Min receive: [MinAmountout]
πŸ“‰ Price Impact: [Price Impact]
`, }, @@ -1053,14 +905,14 @@ export const KAI_ACTIONS: ListActions = { { type: ActionType.HTML, title: ` -
πŸ”„ You're awapping: ${arg.amountIn} of ${arg.tokenIn.symbol}, est. ${quoteSymbol} value ${ - arg.tokenIn.token.priceSell * arg.amountIn - }
-
πŸ’° For: ${(arg.amountIn * arg.tokenIn.token.priceSell) / arg.tokenOut.token.priceBuy} of ${ - arg.tokenOut.symbol - }, est. ${quoteSymbol} value ${arg.amountIn * arg.tokenIn.token.priceSell}
-
βš–οΈ Slippage tolerance: ${answer}%
-
πŸ“ Min receive: [MinAmountout]
+
πŸ”„ You're awapping: ${arg.amountIn} of ${ + arg.tokenIn.symbol + }, est. ${quoteSymbol} value ${arg.tokenIn.token.priceSell * arg.amountIn}
+
πŸ’° For: ${ + (arg.amountIn * arg.tokenIn.token.priceSell) / arg.tokenOut.token.priceBuy + } of ${arg.tokenOut.symbol}, est. ${quoteSymbol} value ${arg.amountIn * arg.tokenIn.token.priceSell}
+
βš–οΈ Slippage tolerance: ${answer}%
+
πŸ“ Min receive: [MinAmountout]
πŸ“‰ Price Impact: [Price Impact]
`, }, From f34f6e95017ba606915fa21e8ec637d5d9f02660 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 27 Sep 2024 10:49:23 +0700 Subject: [PATCH 21/28] Change logic check previous actions & remove uuid --- package.json | 1 - src/components/Kai/KaiPanel.tsx | 48 ++++++++++++++++++++++++++++----- yarn.lock | 5 ---- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index f901885e9f..039885d022 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,6 @@ "swiper": "^8.4.4", "ua-parser-js": "^1.0.33", "util": "^0.12.5", - "uuid": "^10.0.0", "viem": "^2.20.0", "vite-plugin-env-compatible": "^1.1.1", "wagmi": "^2.12.7", diff --git a/src/components/Kai/KaiPanel.tsx b/src/components/Kai/KaiPanel.tsx index f1362d8934..e5e274260d 100644 --- a/src/components/Kai/KaiPanel.tsx +++ b/src/components/Kai/KaiPanel.tsx @@ -2,7 +2,6 @@ import { ChainId } from '@kyberswap/ks-sdk-core' import { ChangeEvent, KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react' import { Flex } from 'rebass' import { useGetQuoteByChainQuery } from 'services/marketOverview' -import { v4 as uuidv4 } from 'uuid' import { ReactComponent as KaiAvatar } from 'assets/svg/kai_avatar.svg' import NavGroup from 'components/Header/groups/NavGroup' @@ -11,6 +10,7 @@ import { MAINNET_NETWORKS } from 'constants/networks' import { useAllTokens } from 'hooks/Tokens' import { NETWORKS_INFO } from 'hooks/useChainsConfig' +// import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo' import { ActionType, KAI_ACTIONS, KaiAction, KaiOption } from './actions' import { ActionButton, @@ -47,11 +47,18 @@ const KaiPanel = () => { const [chatPlaceHolderText, setChatPlaceHolderText] = useState(DEFAULT_CHAT_PLACEHOLDER_TEXT) const [loading, setLoading] = useState(false) const [loadingText, setLoadingText] = useState(DEFAULT_LOADING_TEXT) - const [listActions, setListActions] = useState( - [KAI_ACTIONS.MAIN_MENU].map(item => ({ ...item, uuid: uuidv4() })), - ) + const [listActions, setListActions] = useState([KAI_ACTIONS.MAIN_MENU]) const [chainId, setChainId] = useState(DEFAULT_CHAIN_ID) + // const [swapData, setSwapData] = useState({ + // currencyIn: null, + // currencyOut: null, + // parsedAmount: null, + // isProcessingSwap: false, + // customChain: chainId, + // clientId: undefined, + // }) + const whitelistTokens = useAllTokens(true, chainId) const whitelistTokenAddress = useMemo(() => Object.keys(whitelistTokens), [whitelistTokens]) @@ -73,6 +80,19 @@ const KaiPanel = () => { ) }, [listActions]) + const lastActiveActionIndex = useMemo(() => { + const clonelistActions = [...listActions] + let index = clonelistActions.length - 1 + for (let i = clonelistActions.length - 1; i >= 0; i--) { + if (clonelistActions[i].type !== ActionType.INVALID || clonelistActions[i].type !== ActionType.USER_MESSAGE) { + index = i + break + } + } + + return index + }, [listActions]) + const onSubmitChat = (text: string) => { if (loading || !lastAction) return if (lastAction.loadingText) setLoadingText(lastAction.loadingText) @@ -87,8 +107,7 @@ const KaiPanel = () => { const onChangeListActions = (newActions: KaiAction[]) => { const cloneListActions = [...listActions] - const newActionsWithUuid = newActions.map(item => ({ ...item, uuid: uuidv4() })) - setListActions(cloneListActions.concat(newActionsWithUuid)) + setListActions(cloneListActions.concat(newActions)) } const getActionResponse = async () => { @@ -103,11 +122,26 @@ const KaiPanel = () => { quoteSymbol, })) || [] if (newActions.length) onChangeListActions(newActions) + + // if (newActions.length) { + // const firstAction = newActions[0] + // if (firstAction.callHook && firstAction.callHook === CallHook.SWAP) { + // console.log(firstAction.arg) + // console.log(new WrappedTokenInfo(firstAction.arg.tokenIn)) + // return + // } + // onChangeListActions(newActions) + // } + setLoading(false) setLoadingText(DEFAULT_LOADING_TEXT) } } + // useEffect(() => { + // console.log('swapData', swapData) + // }, [swapData]) + useEffect(() => { if (lastAction?.placeholder) setChatPlaceHolderText(lastAction.placeholder) else setChatPlaceHolderText(DEFAULT_CHAT_PLACEHOLDER_TEXT) @@ -130,7 +164,7 @@ const KaiPanel = () => {
GM! What can I do for you today? πŸ‘‹
{listActions.map((action: KaiAction, index: number) => { - const disabled = !action.uuid || !lastAction?.uuid || action.uuid !== lastAction.uuid + const disabled = index !== lastActiveActionIndex return action.type === ActionType.MAIN_OPTION ? ( diff --git a/yarn.lock b/yarn.lock index 697798ac2c..6d1abb2957 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19406,11 +19406,6 @@ utrie@^1.0.2: dependencies: base64-arraybuffer "^1.0.2" -uuid@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" - integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== - uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" From 33e27e627ed73cab6c46e9be1b57ee9bde7937fd Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 27 Sep 2024 12:01:51 +0700 Subject: [PATCH 22/28] Show address for token info --- src/components/Kai/KaiPanel.tsx | 24 --------------- src/components/Kai/actions.ts | 52 +++++++++++++++++++++++---------- src/components/Kai/styled.tsx | 4 +-- 3 files changed, 39 insertions(+), 41 deletions(-) diff --git a/src/components/Kai/KaiPanel.tsx b/src/components/Kai/KaiPanel.tsx index e5e274260d..c36428ed42 100644 --- a/src/components/Kai/KaiPanel.tsx +++ b/src/components/Kai/KaiPanel.tsx @@ -10,7 +10,6 @@ import { MAINNET_NETWORKS } from 'constants/networks' import { useAllTokens } from 'hooks/Tokens' import { NETWORKS_INFO } from 'hooks/useChainsConfig' -// import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo' import { ActionType, KAI_ACTIONS, KaiAction, KaiOption } from './actions' import { ActionButton, @@ -50,15 +49,6 @@ const KaiPanel = () => { const [listActions, setListActions] = useState([KAI_ACTIONS.MAIN_MENU]) const [chainId, setChainId] = useState(DEFAULT_CHAIN_ID) - // const [swapData, setSwapData] = useState({ - // currencyIn: null, - // currencyOut: null, - // parsedAmount: null, - // isProcessingSwap: false, - // customChain: chainId, - // clientId: undefined, - // }) - const whitelistTokens = useAllTokens(true, chainId) const whitelistTokenAddress = useMemo(() => Object.keys(whitelistTokens), [whitelistTokens]) @@ -123,25 +113,11 @@ const KaiPanel = () => { })) || [] if (newActions.length) onChangeListActions(newActions) - // if (newActions.length) { - // const firstAction = newActions[0] - // if (firstAction.callHook && firstAction.callHook === CallHook.SWAP) { - // console.log(firstAction.arg) - // console.log(new WrappedTokenInfo(firstAction.arg.tokenIn)) - // return - // } - // onChangeListActions(newActions) - // } - setLoading(false) setLoadingText(DEFAULT_LOADING_TEXT) } } - // useEffect(() => { - // console.log('swapData', swapData) - // }, [swapData]) - useEffect(() => { if (lastAction?.placeholder) setChatPlaceHolderText(lastAction.placeholder) else setChatPlaceHolderText(DEFAULT_CHAT_PLACEHOLDER_TEXT) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index a7449dbf25..e2ea0ab404 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -229,6 +229,7 @@ export const KAI_ACTIONS: ListActions = { if (result.length === 1) { const token = result[0] + const showAddress = answer !== token.token.address.toLowerCase() return [ { @@ -238,6 +239,9 @@ export const KAI_ACTIONS: ListActions = { { type: ActionType.HTML, title: ` +
${ + showAddress ? 'πŸ“Œ Token contract: ' + token.token.address : '' + }
πŸ“ˆ Buy Price: ${ token.token.priceBuy ? `${formatDisplayNumber(token.token.priceBuy, { @@ -306,7 +310,9 @@ export const KAI_ACTIONS: ListActions = { if (tokenSymbolSelected === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] const token = result.find((item: any) => item.symbol.toLowerCase() === tokenSymbolSelected) - if (token) + if (token) { + const showAddress = answer !== token.token.address.toLowerCase() + return [ { title: `Here’s what I’ve got for ${tokenSymbolSelected}`, @@ -315,6 +321,9 @@ export const KAI_ACTIONS: ListActions = { { type: ActionType.HTML, title: ` +
${ + showAddress ? 'πŸ“Œ Token contract: ' + token.token.address : '' + }
πŸ“ˆ Buy Price: ${ token.token.priceBuy ? `${formatDisplayNumber(token.token.priceBuy, { @@ -368,6 +377,7 @@ export const KAI_ACTIONS: ListActions = { KAI_ACTIONS.WOULD_LIKE_TO_DO_SOMETHING_ELSE, KAI_ACTIONS.DO_SOMETHING_AFTER_CHECK_PRICE, ] + } return [KAI_ACTIONS.TOKEN_NOT_FOUND, KAI_ACTIONS.INVALID_BACK_TO_MENU] }, @@ -505,6 +515,9 @@ export const KAI_ACTIONS: ListActions = { { type: ActionType.HTML, title: ` +
${ + 'πŸ“Œ Token contract: ' + token.token.address + }
πŸ“ˆ Buy Price: ${ token.token.priceBuy ? `${formatDisplayNumber(token.token.priceBuy, { @@ -622,13 +635,14 @@ export const KAI_ACTIONS: ListActions = { if (result.length === 1) { const token = result[0] + const showAddress = answer !== token.token.address.toLowerCase() return [ { type: ActionType.HTML, title: ` -
πŸ“Œ Token contract: ${ - token.token.address +
πŸ“Œ ${ + showAddress ? `Token contract: ${token.token.address}` : token.symbol }
πŸ“ˆ Sell Price: ${ token.token.priceSell @@ -662,13 +676,15 @@ export const KAI_ACTIONS: ListActions = { if (tokenSymbolSelected === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] const token = result.find((item: any) => item.symbol.toLowerCase() === tokenSymbolSelected) - if (token) + if (token) { + const showAddress = answer !== token.token.address.toLowerCase() + return [ { type: ActionType.HTML, title: ` -
πŸ“Œ Token contract: ${ - token.token.address +
πŸ“Œ ${ + showAddress ? `Token contract: ${token.token.address}` : token.symbol }
πŸ“ˆ Sell Price: ${ token.token.priceSell @@ -687,6 +703,7 @@ export const KAI_ACTIONS: ListActions = { }, }, ] + } return [KAI_ACTIONS.TOKEN_NOT_FOUND, KAI_ACTIONS.INVALID_BACK_TO_MENU] }, @@ -770,13 +787,14 @@ export const KAI_ACTIONS: ListActions = { if (result.length === 1) { const token = result[0] + const showAddress = answer !== token.token.address.toLowerCase() return [ { type: ActionType.HTML, title: ` -
πŸ“Œ Token contract: ${ - token.token.address +
πŸ“Œ ${ + showAddress ? `Token contract: ${token.token.address}` : token.symbol }
πŸ“ˆ Buy Price: ${ token.token.priceBuy @@ -812,13 +830,16 @@ export const KAI_ACTIONS: ListActions = { if (tokenSymbolSelected === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] const token = result.find((item: any) => item.symbol.toLowerCase() === tokenSymbolSelected) - if (token) + + if (token) { + const showAddress = answer !== token.token.address.toLowerCase() + return [ { type: ActionType.HTML, title: ` -
πŸ“Œ Token contract: ${ - token.token.address +
πŸ“Œ ${ + showAddress ? `Token contract: ${token.token.address}` : token.symbol }
πŸ“ˆ Buy Price: ${ token.token.priceBuy @@ -839,6 +860,7 @@ export const KAI_ACTIONS: ListActions = { }, }, ] + } return [KAI_ACTIONS.TOKEN_NOT_FOUND, KAI_ACTIONS.INVALID_BACK_TO_MENU] }, @@ -878,8 +900,8 @@ export const KAI_ACTIONS: ListActions = { (arg.amountIn * arg.tokenIn.token.priceSell) / arg.tokenIn.token.priceBuy } of ${arg.tokenOut.symbol}, est. ${quoteSymbol} value ${arg.amountIn * arg.tokenIn.token.priceSell}
βš–οΈ Slippage tolerance: ${slippage}%
-
πŸ“ Min receive: [MinAmountout]
-
πŸ“‰ Price Impact: [Price Impact]
+
πŸ“ Min receive: --
+
πŸ“‰ Price Impact: --
`, }, KAI_ACTIONS.CONFIRM_SWAP_TOKEN_TEXT, @@ -912,8 +934,8 @@ export const KAI_ACTIONS: ListActions = { (arg.amountIn * arg.tokenIn.token.priceSell) / arg.tokenOut.token.priceBuy } of ${arg.tokenOut.symbol}, est. ${quoteSymbol} value ${arg.amountIn * arg.tokenIn.token.priceSell}
βš–οΈ Slippage tolerance: ${answer}%
-
πŸ“ Min receive: [MinAmountout]
-
πŸ“‰ Price Impact: [Price Impact]
+
πŸ“ Min receive: --
+
πŸ“‰ Price Impact: --
`, }, KAI_ACTIONS.CONFIRM_SWAP_TOKEN_TEXT, diff --git a/src/components/Kai/styled.tsx b/src/components/Kai/styled.tsx index e310c7dc92..c82b568506 100644 --- a/src/components/Kai/styled.tsx +++ b/src/components/Kai/styled.tsx @@ -41,7 +41,7 @@ export const ModalContent = styled.div` background: ${({ theme }) => theme.tableHeader}; padding: 20px 24px 26px; border-radius: 12px; - width: 348px; + width: 380px; ${({ theme }) => theme.mediaWidth.upToExtraSmall` width: calc(100vw - 2rem); @@ -222,7 +222,7 @@ export const Loader = styled.div` ` export const ChatPanel = styled.div` - max-height: 360px; + max-height: 415px; overflow: scroll; ${({ theme }) => theme.mediaWidth.upToExtraSmall` From 230620f3b49d2c8e644dc74e2a7ba0160860a911 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 27 Sep 2024 12:13:18 +0700 Subject: [PATCH 23/28] Fix Kai message when swap & add limit order action --- src/components/Kai/KaiPanel.tsx | 2 +- src/components/Kai/actions.ts | 28 ++++++++++++++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/components/Kai/KaiPanel.tsx b/src/components/Kai/KaiPanel.tsx index c36428ed42..63decccab8 100644 --- a/src/components/Kai/KaiPanel.tsx +++ b/src/components/Kai/KaiPanel.tsx @@ -74,7 +74,7 @@ const KaiPanel = () => { const clonelistActions = [...listActions] let index = clonelistActions.length - 1 for (let i = clonelistActions.length - 1; i >= 0; i--) { - if (clonelistActions[i].type !== ActionType.INVALID || clonelistActions[i].type !== ActionType.USER_MESSAGE) { + if (clonelistActions[i].type !== ActionType.INVALID && clonelistActions[i].type !== ActionType.USER_MESSAGE) { index = i break } diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index e2ea0ab404..d34af83cbc 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -57,7 +57,11 @@ const KAI_OPTIONS: ListOptions = { }, SWAP_TOKEN: { title: 'Buy/Sell tokens', - space: Space.FULL_WIDTH, + space: Space.HALF_WIDTH, + }, + LIMIT_ORDER: { + title: 'Limit Order', + space: Space.HALF_WIDTH, }, ADD_LIQUIDITY: { title: 'Add liquidity', @@ -97,6 +101,7 @@ export const MAIN_MENU: KaiOption[] = [ KAI_OPTIONS.CHECK_TOKEN_PRICE, KAI_OPTIONS.SEE_MARKET_TRENDS, KAI_OPTIONS.SWAP_TOKEN, + KAI_OPTIONS.LIMIT_ORDER, KAI_OPTIONS.FIND_HIGH_APY_POOLS, KAI_OPTIONS.ADD_LIQUIDITY, ] @@ -131,7 +136,11 @@ export const KAI_ACTIONS: ListActions = { }, DO_SOMETHING_AFTER_CHECK_PRICE: { type: ActionType.OPTION, - data: [KAI_OPTIONS.SWAP_TOKEN, KAI_OPTIONS.SEARCH_ANOTHER_TOKEN, KAI_OPTIONS.BACK_TO_MENU], + data: [ + { ...KAI_OPTIONS.SWAP_TOKEN, space: Space.FULL_WIDTH }, + KAI_OPTIONS.SEARCH_ANOTHER_TOKEN, + KAI_OPTIONS.BACK_TO_MENU, + ], response: ({ answer }: { answer: string }) => { if (answer === KAI_OPTIONS.SWAP_TOKEN.title.toLowerCase()) return [KAI_ACTIONS.COMING_SOON, KAI_ACTIONS.INVALID_BACK_TO_MENU] @@ -498,7 +507,10 @@ export const KAI_ACTIONS: ListActions = { }, { type: ActionType.OPTION, - data: resultToActionData.concat([KAI_OPTIONS.SWAP_TOKEN, KAI_OPTIONS.BACK_TO_MENU]), + data: resultToActionData.concat([ + { ...KAI_OPTIONS.SWAP_TOKEN, space: Space.FULL_WIDTH }, + KAI_OPTIONS.BACK_TO_MENU, + ]), response: ({ answer, quoteSymbol }: { answer: string; quoteSymbol: string }) => { if (answer === KAI_OPTIONS.BACK_TO_MENU.title.toLowerCase()) return [KAI_ACTIONS.MAIN_MENU] if (answer === KAI_OPTIONS.SWAP_TOKEN.title.toLowerCase()) @@ -587,7 +599,7 @@ export const KAI_ACTIONS: ListActions = { type: ActionType.TEXT, }, SWAP_INPUT_TOKEN_IN: { - title: 'πŸ‘‰ Enter the token in you want to swap', + title: 'πŸ‘‰ Input the token you want to sell', type: ActionType.TEXT, placeholder: 'Enter the token in', response: async ({ @@ -718,7 +730,7 @@ export const KAI_ACTIONS: ListActions = { }, }, SWAP_INPUT_AMOUNT_IN: { - title: 'πŸ‘‰ Enter the amount in you want to swap', + title: 'πŸ‘‰ Input amount of token you want to sell', type: ActionType.TEXT, placeholder: 'Enter the amount in', response: ({ answer, arg }: { answer: string; arg: any }) => { @@ -737,7 +749,7 @@ export const KAI_ACTIONS: ListActions = { }, }, SWAP_INPUT_TOKEN_OUT: { - title: 'πŸ‘‰ Enter the token out you want to swap', + title: 'πŸ‘‰ Input the token you want to buy', type: ActionType.TEXT, placeholder: 'Enter the token out', response: async ({ @@ -875,7 +887,7 @@ export const KAI_ACTIONS: ListActions = { }, }, SWAP_INPUT_SLIPPAGE_TEXT: { - title: 'πŸ‘‰ Choose the max slippage you can accept', + title: 'πŸ‘‰ Choose max slippage you want to set', type: ActionType.TEXT, }, SWAP_INPUT_SLIPPAGE: { @@ -916,7 +928,7 @@ export const KAI_ACTIONS: ListActions = { }, }, CUSTOM_MAX_SLIPPAGE: { - title: 'πŸ‘‰ Enter the max slippage you can accept (in percent)', + title: 'πŸ‘‰ Input max slippage you can to set (in percent)', type: ActionType.TEXT, placeholder: 'Enter the max slippage', response: ({ answer, arg, quoteSymbol }: { answer: string; arg: any; quoteSymbol: string }) => { From a8084bd19a6e1d3607e8c5421d79a5a88a7841bb Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 27 Sep 2024 13:05:46 +0700 Subject: [PATCH 24/28] Fix width --- src/components/Kai/styled.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Kai/styled.tsx b/src/components/Kai/styled.tsx index c82b568506..f58f11a83a 100644 --- a/src/components/Kai/styled.tsx +++ b/src/components/Kai/styled.tsx @@ -41,7 +41,7 @@ export const ModalContent = styled.div` background: ${({ theme }) => theme.tableHeader}; padding: 20px 24px 26px; border-radius: 12px; - width: 380px; + width: 390px; ${({ theme }) => theme.mediaWidth.upToExtraSmall` width: calc(100vw - 2rem); From fa05a09de972174a8555994a65b59402c8435e15 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 27 Sep 2024 13:36:24 +0700 Subject: [PATCH 25/28] Fix width --- src/components/Kai/styled.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Kai/styled.tsx b/src/components/Kai/styled.tsx index f58f11a83a..0684d580c8 100644 --- a/src/components/Kai/styled.tsx +++ b/src/components/Kai/styled.tsx @@ -41,7 +41,7 @@ export const ModalContent = styled.div` background: ${({ theme }) => theme.tableHeader}; padding: 20px 24px 26px; border-radius: 12px; - width: 390px; + width: 395px; ${({ theme }) => theme.mediaWidth.upToExtraSmall` width: calc(100vw - 2rem); From dcf6cf01e4fd86e3055b10422246ac97abb1b722 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 27 Sep 2024 13:47:45 +0700 Subject: [PATCH 26/28] Change prompt --- src/components/Kai/actions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index d34af83cbc..ac07760e93 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -599,7 +599,7 @@ export const KAI_ACTIONS: ListActions = { type: ActionType.TEXT, }, SWAP_INPUT_TOKEN_IN: { - title: 'πŸ‘‰ Input the token you want to sell', + title: 'πŸ‘‰ Input the token you want to sell (Token in)', type: ActionType.TEXT, placeholder: 'Enter the token in', response: async ({ @@ -749,7 +749,7 @@ export const KAI_ACTIONS: ListActions = { }, }, SWAP_INPUT_TOKEN_OUT: { - title: 'πŸ‘‰ Input the token you want to buy', + title: 'πŸ‘‰ Input the token you want to buy (Token out)', type: ActionType.TEXT, placeholder: 'Enter the token out', response: async ({ From 5605a5951fd1ad5ca4557cb4b97118a054f20376 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 27 Sep 2024 14:36:23 +0700 Subject: [PATCH 27/28] Fix top gainer filter params --- src/components/Kai/actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Kai/actions.ts b/src/components/Kai/actions.ts index ac07760e93..9bd75a4c2d 100644 --- a/src/components/Kai/actions.ts +++ b/src/components/Kai/actions.ts @@ -445,7 +445,7 @@ export const KAI_ACTIONS: ListActions = { chainIds: chainId, sort: arg === KAI_OPTIONS.TOP_GAINERS.title.toLowerCase() - ? 'price_sell_change_24h-1 desc' + ? `price_sell_change_24h-${chainId} desc` : arg === KAI_OPTIONS.TOP_VOLUME.title.toLowerCase() ? 'volume_24h desc' : '', From 17dd8e4c96d835c2a7f3381634b5dfe620d52edb Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 19 Sep 2025 11:59:59 +0700 Subject: [PATCH 28/28] fix after merge with monorepo --- .../src/assets/svg/ic_send.svg | 8 +++++--- .../src/assets/svg/kai_avatar.svg | 20 +++++++++++++------ .../src/assets/svg/kai_avatar2.svg | 20 +++++++++++++------ .../src}/components/Kai/KaiPanel.tsx | 0 .../src}/components/Kai/actions.ts | 0 .../src}/components/Kai/index.tsx | 0 .../src}/components/Kai/styled.tsx | 0 .../src}/components/Kai/utils.ts | 0 8 files changed, 33 insertions(+), 15 deletions(-) rename {src => apps/kyberswap-interface/src}/components/Kai/KaiPanel.tsx (100%) rename {src => apps/kyberswap-interface/src}/components/Kai/actions.ts (100%) rename {src => apps/kyberswap-interface/src}/components/Kai/index.tsx (100%) rename {src => apps/kyberswap-interface/src}/components/Kai/styled.tsx (100%) rename {src => apps/kyberswap-interface/src}/components/Kai/utils.ts (100%) diff --git a/apps/kyberswap-interface/src/assets/svg/ic_send.svg b/apps/kyberswap-interface/src/assets/svg/ic_send.svg index 5221354aeb..fd8569a908 100644 --- a/apps/kyberswap-interface/src/assets/svg/ic_send.svg +++ b/apps/kyberswap-interface/src/assets/svg/ic_send.svg @@ -1,4 +1,6 @@ - - - + + + \ No newline at end of file diff --git a/apps/kyberswap-interface/src/assets/svg/kai_avatar.svg b/apps/kyberswap-interface/src/assets/svg/kai_avatar.svg index 76b7104300..e80d07a41e 100644 --- a/apps/kyberswap-interface/src/assets/svg/kai_avatar.svg +++ b/apps/kyberswap-interface/src/assets/svg/kai_avatar.svg @@ -1,7 +1,15 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/apps/kyberswap-interface/src/assets/svg/kai_avatar2.svg b/apps/kyberswap-interface/src/assets/svg/kai_avatar2.svg index d47a1a1d74..c9e52b4a20 100644 --- a/apps/kyberswap-interface/src/assets/svg/kai_avatar2.svg +++ b/apps/kyberswap-interface/src/assets/svg/kai_avatar2.svg @@ -1,7 +1,15 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/src/components/Kai/KaiPanel.tsx b/apps/kyberswap-interface/src/components/Kai/KaiPanel.tsx similarity index 100% rename from src/components/Kai/KaiPanel.tsx rename to apps/kyberswap-interface/src/components/Kai/KaiPanel.tsx diff --git a/src/components/Kai/actions.ts b/apps/kyberswap-interface/src/components/Kai/actions.ts similarity index 100% rename from src/components/Kai/actions.ts rename to apps/kyberswap-interface/src/components/Kai/actions.ts diff --git a/src/components/Kai/index.tsx b/apps/kyberswap-interface/src/components/Kai/index.tsx similarity index 100% rename from src/components/Kai/index.tsx rename to apps/kyberswap-interface/src/components/Kai/index.tsx diff --git a/src/components/Kai/styled.tsx b/apps/kyberswap-interface/src/components/Kai/styled.tsx similarity index 100% rename from src/components/Kai/styled.tsx rename to apps/kyberswap-interface/src/components/Kai/styled.tsx diff --git a/src/components/Kai/utils.ts b/apps/kyberswap-interface/src/components/Kai/utils.ts similarity index 100% rename from src/components/Kai/utils.ts rename to apps/kyberswap-interface/src/components/Kai/utils.ts