Skip to content

Commit ae700d8

Browse files
committed
feat: convert UI for agentic chat to wallet view
1 parent 71978ab commit ae700d8

22 files changed

+781
-153
lines changed

src/assets/translations/en/main.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,15 @@
343343
"pending": "Pending"
344344
}
345345
}
346-
}
346+
},
347+
"chatHistory": "Chat History",
348+
"newChat": "New Chat",
349+
"deleteConversation": "Delete Conversation",
350+
"deleteConfirmation": "Are you sure you want to delete this conversation?",
351+
"noConversations": "No conversations yet",
352+
"newConversation": "New Conversation",
353+
"searchChats": "Search your chats...",
354+
"noMatchingConversations": "No matching conversations"
347355
},
348356
"quickBuy": {
349357
"title": "Quick Buy %{assetOnChain}",

src/components/Layout/Header/NavBar/DrawerWallet.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Drawer, DrawerContent, DrawerOverlay } from '@chakra-ui/react'
22
import type { FC } from 'react'
3-
import { memo, useCallback, useEffect, useMemo } from 'react'
3+
import { memo, useCallback, useMemo } from 'react'
44
import { MemoryRouter, Route, Routes, useNavigate } from 'react-router-dom'
55

66
import { DrawerSettings } from './DrawerSettings'
@@ -59,19 +59,19 @@ export const DrawerWalletInner: FC<DrawerWalletInnerProps> = memo(({ onClose, is
5959
})
6060

6161
export const DrawerWallet: FC = memo(() => {
62-
const { isOpen, close: onClose } = useModal('walletDrawer')
62+
const { isOpen, close } = useModal('walletDrawer')
6363
const dispatch = useAppDispatch()
64+
65+
const onClose = useCallback(() => {
66+
dispatch(agenticChatSlice.actions.endChat())
67+
close()
68+
}, [dispatch, close])
69+
6470
const { modalContentProps, overlayProps, modalProps } = useModalRegistration({
6571
isOpen,
6672
onClose,
6773
})
6874

69-
useEffect(() => {
70-
if (!isOpen) {
71-
dispatch(agenticChatSlice.actions.closeChat())
72-
}
73-
}, [isOpen, dispatch])
74-
7575
return (
7676
<Drawer placement='right' size='sm' {...modalProps}>
7777
<DrawerOverlay {...overlayProps} />

src/components/Layout/Header/NavBar/DrawerWalletDashboard.tsx

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ import { WalletBalanceChange } from '@/components/WalletBalanceChange/WalletBala
2727
import { WalletActions } from '@/context/WalletProvider/actions'
2828
import { DrawerChatButton } from '@/features/agenticChat/components/DrawerChatButton'
2929
import { DrawerChatContent } from '@/features/agenticChat/components/DrawerChatContent'
30+
import { DrawerChatHistory } from '@/features/agenticChat/components/DrawerChatHistory'
3031
import { useModal } from '@/hooks/useModal/useModal'
3132
import { useWallet } from '@/hooks/useWallet/useWallet'
3233
import { agenticChatSlice } from '@/state/slices/agenticChatSlice/agenticChatSlice'
33-
import { useAppSelector } from '@/state/store'
34+
import { useAppDispatch, useAppSelector } from '@/state/store'
3435
import { makeSuspenseful } from '@/utils/makeSuspenseful'
3536

3637
const tabSpinnerStyle = { height: '200px' }
@@ -127,9 +128,11 @@ type DrawerWalletDashboardProps = {
127128
export const DrawerWalletDashboard: FC<DrawerWalletDashboardProps> = memo(
128129
({ onClose, onSettingsClick, isOpen }) => {
129130
const translate = useTranslate()
131+
const reduxDispatch = useAppDispatch()
130132
const send = useModal('send')
131133
const receive = useModal('receive')
132134
const isChatOpen = useAppSelector(agenticChatSlice.selectors.selectIsChatOpen)
135+
const isChatHistoryOpen = useAppSelector(agenticChatSlice.selectors.selectIsChatHistoryOpen)
133136

134137
const [activeTabIndex, setActiveTabIndex] = useState(0)
135138
const [loadedTabs, setLoadedTabs] = useState(new Set<number>())
@@ -176,22 +179,29 @@ export const DrawerWalletDashboard: FC<DrawerWalletDashboardProps> = memo(
176179
onClose()
177180
}, [disconnect, onClose])
178181

182+
const handleBackFromChat = useCallback(() => {
183+
reduxDispatch(agenticChatSlice.actions.endChat())
184+
}, [reduxDispatch])
185+
179186
return (
180187
<>
181-
{isChatOpen ? (
182-
<DrawerChatContent />
183-
) : (
188+
<DrawerWalletHeader
189+
walletInfo={walletInfo}
190+
isConnected={isConnected}
191+
isLocked={isLocked}
192+
connectedType={connectedType}
193+
onDisconnect={handleDisconnect}
194+
onSwitchProvider={handleSwitchProvider}
195+
onClose={onClose}
196+
onSettingsClick={onSettingsClick}
197+
isChatOpen={isChatOpen}
198+
onBackFromChat={handleBackFromChat}
199+
/>
200+
201+
{isChatOpen && isChatHistoryOpen && <DrawerChatHistory />}
202+
{isChatOpen && !isChatHistoryOpen && <DrawerChatContent />}
203+
{!isChatOpen && (
184204
<>
185-
<DrawerWalletHeader
186-
walletInfo={walletInfo}
187-
isConnected={isConnected}
188-
isLocked={isLocked}
189-
connectedType={connectedType}
190-
onDisconnect={handleDisconnect}
191-
onSwitchProvider={handleSwitchProvider}
192-
onClose={onClose}
193-
onSettingsClick={onSettingsClick}
194-
/>
195205
<Box pt={6} pb={8}>
196206
<Suspense fallback={<Skeleton height='36px' width='100px' mx='auto' />}>
197207
<WalletBalanceChange showErroredAccounts={false} />

src/components/Layout/Header/NavBar/DrawerWalletHeader.tsx

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
} from '@chakra-ui/react'
1414
import type { FC } from 'react'
1515
import { memo, useCallback, useMemo } from 'react'
16-
import { TbDots, TbEyeOff, TbSettings } from 'react-icons/tb'
16+
import { FiArrowLeft } from 'react-icons/fi'
17+
import { TbDots, TbEyeOff, TbHistory, TbSettings } from 'react-icons/tb'
1718
import { useTranslate } from 'react-polyglot'
1819
import { useNavigate } from 'react-router-dom'
1920

@@ -25,14 +26,16 @@ import type { InitialState } from '@/context/WalletProvider/WalletProvider'
2526
import { useModal } from '@/hooks/useModal/useModal'
2627
import { useMipdProviders } from '@/lib/mipd'
2728
import { ProfileAvatar } from '@/pages/Dashboard/components/ProfileAvatar/ProfileAvatar'
29+
import { agenticChatSlice } from '@/state/slices/agenticChatSlice/agenticChatSlice'
2830
import { gridplusSlice } from '@/state/slices/gridplusSlice/gridplusSlice'
2931
import { selectWalletRdns } from '@/state/slices/localWalletSlice/selectors'
30-
import { useAppSelector } from '@/state/store'
32+
import { useAppDispatch, useAppSelector } from '@/state/store'
3133

3234
const settingsIcon = <TbSettings />
3335
const dotsIcon = <Icon as={TbDots} />
3436
const eyeOffIcon = <Icon as={TbEyeOff} />
3537
const qrCodeIcon = <QRCodeIcon />
38+
const historyIcon = <TbHistory />
3639

3740
type DrawerHeaderProps = {
3841
walletInfo: InitialState['walletInfo']
@@ -43,6 +46,8 @@ type DrawerHeaderProps = {
4346
onSwitchProvider: () => void
4447
onClose?: () => void
4548
onSettingsClick?: () => void
49+
isChatOpen?: boolean
50+
onBackFromChat?: () => void
4651
}
4752

4853
export const DrawerWalletHeader: FC<DrawerHeaderProps> = memo(
@@ -54,11 +59,15 @@ export const DrawerWalletHeader: FC<DrawerHeaderProps> = memo(
5459
onDisconnect,
5560
onSwitchProvider,
5661
onSettingsClick,
62+
isChatOpen,
63+
onBackFromChat,
5764
}) => {
5865
const translate = useTranslate()
66+
const dispatch = useAppDispatch()
5967
const settings = useModal('settings')
6068
const qrCode = useModal('qrCode')
6169
const navigate = useNavigate()
70+
const isChatHistoryOpen = useAppSelector(agenticChatSlice.selectors.selectIsChatHistoryOpen)
6271

6372
const maybeRdns = useAppSelector(selectWalletRdns)
6473
const activeSafeCard = useAppSelector(gridplusSlice.selectors.selectActiveSafeCard)
@@ -87,6 +96,10 @@ export const DrawerWalletHeader: FC<DrawerHeaderProps> = memo(
8796
qrCode.open({})
8897
}, [qrCode])
8998

99+
const handleChatHistoryClick = useCallback(() => {
100+
dispatch(agenticChatSlice.actions.openChatHistory())
101+
}, [dispatch])
102+
90103
const handleManageHiddenAssetsClick = useCallback(() => {
91104
navigate('/manage-hidden-assets')
92105
}, [navigate])
@@ -107,20 +120,44 @@ export const DrawerWalletHeader: FC<DrawerHeaderProps> = memo(
107120
if (!isConnected || isLocked || !walletInfo) return null
108121

109122
return (
110-
<Flex align='center' px={4} pt={4} justify='space-between'>
123+
<Flex align='center' px={4} pt={4} pb={isChatOpen ? 4 : 0} justify='space-between'>
111124
<Flex align='center' gap={2}>
125+
{isChatOpen && onBackFromChat && (
126+
<IconButton
127+
icon={<FiArrowLeft />}
128+
aria-label={translate('common.back')}
129+
onClick={
130+
isChatHistoryOpen
131+
? () => dispatch(agenticChatSlice.actions.closeChatHistory())
132+
: onBackFromChat
133+
}
134+
variant='ghost'
135+
size='sm'
136+
/>
137+
)}
112138
<ProfileAvatar size='md' borderRadius='full' />
113139
<Text fontWeight='medium'>{label}</Text>
114140
</Flex>
115141
<Flex gap={2}>
116-
<IconButton
117-
aria-label={translate('modals.send.qrCode')}
118-
isRound
119-
fontSize='lg'
120-
icon={qrCodeIcon}
121-
size='md'
122-
onClick={handleQrCodeClick}
123-
/>
142+
{isChatOpen ? (
143+
<IconButton
144+
aria-label={translate('agenticChat.chatHistory')}
145+
isRound
146+
fontSize='lg'
147+
icon={historyIcon}
148+
size='md'
149+
onClick={handleChatHistoryClick}
150+
/>
151+
) : (
152+
<IconButton
153+
aria-label={translate('modals.send.qrCode')}
154+
isRound
155+
fontSize='lg'
156+
icon={qrCodeIcon}
157+
size='md'
158+
onClick={handleQrCodeClick}
159+
/>
160+
)}
124161
<IconButton
125162
aria-label='Settings'
126163
isRound

src/features/agenticChat/components/Chat.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ const MessageItem = memo(({ message, userBg }: MessageItemProps) => {
5353
{toolInvocations.map(toolPart => (
5454
<ToolInvocationRenderer key={toolPart.toolCallId} toolPart={toolPart} />
5555
))}
56-
{textContent.trim().length > 0 && <Markdown>{textContent}</Markdown>}
56+
{textContent.trim().length > 0 && (
57+
<Box mt={toolInvocations.length > 0 ? 3 : 0}>
58+
<Markdown>{textContent}</Markdown>
59+
</Box>
60+
)}
5761
</Box>
5862
)
5963
})
@@ -67,7 +71,7 @@ export const Chat = ({ chat }: ChatProps) => {
6771
const bottomRef = useRef<HTMLDivElement>(null)
6872
const shouldAutoScrollRef = useRef(true)
6973

70-
const userBg = useColorModeValue('blue.50', 'blue.900')
74+
const userBg = useColorModeValue('blue.50', 'blue.600')
7175

7276
useEffect(() => {
7377
const viewport = viewportRef.current
@@ -97,8 +101,11 @@ export const Chat = ({ chat }: ChatProps) => {
97101
gap={3}
98102
flex={1}
99103
overflowY='auto'
100-
p={4}
104+
px={4}
105+
pt={4}
106+
pb={2}
101107
minHeight={0}
108+
className='scroll-container'
102109
>
103110
{isEmpty ? (
104111
<Flex flex={1} alignItems='center' justifyContent='center'>
Lines changed: 44 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Flex, IconButton, Textarea, useColorModeValue } from '@chakra-ui/react'
2-
import { useCallback, useRef } from 'react'
1+
import { Box, Flex, IconButton, Textarea, useColorModeValue } from '@chakra-ui/react'
2+
import { useCallback } from 'react'
33
import { FiSend, FiSquare } from 'react-icons/fi'
44
import { useTranslate } from 'react-polyglot'
55

@@ -15,8 +15,8 @@ type ComposerProps = {
1515
export const Composer = ({ chat }: ComposerProps) => {
1616
const translate = useTranslate()
1717
const { input, handleInputChange, handleSubmit, status, stop } = chat
18-
const textareaRef = useRef<HTMLTextAreaElement>(null)
1918

19+
const composerBg = useColorModeValue('white', 'gray.700')
2020
const borderColor = useColorModeValue('gray.200', 'gray.600')
2121
const isLoading = status === 'streaming'
2222

@@ -32,49 +32,47 @@ export const Composer = ({ chat }: ComposerProps) => {
3232
[input, handleSubmit],
3333
)
3434

35-
const adjustHeight = useCallback(() => {
36-
const textarea = textareaRef.current
37-
if (textarea) {
38-
textarea.style.height = 'auto'
39-
textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`
40-
}
41-
}, [])
42-
43-
const handleChange = useCallback(
44-
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
45-
handleInputChange(e)
46-
adjustHeight()
47-
},
48-
[handleInputChange, adjustHeight],
49-
)
50-
5135
return (
52-
<Flex gap={2} alignItems='flex-end'>
53-
<Textarea
54-
ref={textareaRef}
55-
value={input}
56-
onChange={handleChange}
57-
onKeyDown={handleKeyDown}
58-
placeholder={translate('agenticChat.placeholder')}
59-
resize='none'
60-
minHeight='44px'
61-
maxHeight='200px'
62-
flex={1}
63-
borderColor={borderColor}
64-
_focus={{ borderColor: 'blue.500' }}
65-
autoComplete='new-password'
66-
data-form-type='other'
67-
data-lpignore='true'
68-
data-1p-ignore='true'
69-
/>
70-
<IconButton
71-
icon={isLoading ? STOP_ICON : SEND_ICON}
72-
aria-label={translate(isLoading ? 'agenticChat.stop' : 'agenticChat.send')}
73-
onClick={isLoading ? stop : () => handleSubmit()}
74-
size='sm'
75-
colorScheme='blue'
76-
isDisabled={!isLoading && !input.trim()}
77-
/>
78-
</Flex>
36+
<Box
37+
bg={composerBg}
38+
borderRadius='xl'
39+
border='1px solid'
40+
borderColor={borderColor}
41+
p={2}
42+
boxShadow='sm'
43+
>
44+
<Flex gap={2} alignItems='flex-end'>
45+
<Textarea
46+
value={input}
47+
onChange={handleInputChange}
48+
onKeyDown={handleKeyDown}
49+
placeholder={translate('agenticChat.placeholder')}
50+
resize='none'
51+
rows={1}
52+
py={2}
53+
flex={1}
54+
border='none'
55+
_focus={{ outline: 'none', boxShadow: 'none' }}
56+
_focusVisible={{ outline: 'none', boxShadow: 'none' }}
57+
autoComplete='new-password'
58+
data-form-type='other'
59+
data-lpignore='true'
60+
data-1p-ignore='true'
61+
sx={{
62+
minHeight: '40px',
63+
maxHeight: '200px',
64+
fieldSizing: 'content',
65+
}}
66+
/>
67+
<IconButton
68+
icon={isLoading ? STOP_ICON : SEND_ICON}
69+
aria-label={translate(isLoading ? 'agenticChat.stop' : 'agenticChat.send')}
70+
onClick={isLoading ? stop : () => handleSubmit()}
71+
size='md'
72+
colorScheme='blue'
73+
isDisabled={!isLoading && !input.trim()}
74+
/>
75+
</Flex>
76+
</Box>
7977
)
8078
}

0 commit comments

Comments
 (0)