Skip to content

Commit

Permalink
Merge pull request #27 from coinbase/alissa.crane/qa
Browse files Browse the repository at this point in the history
feat: implement redesign
  • Loading branch information
abcrane123 authored Nov 7, 2024
2 parents 6c71770 + 28ea3d2 commit 851032d
Show file tree
Hide file tree
Showing 12 changed files with 340 additions and 181 deletions.
28 changes: 23 additions & 5 deletions app/components/Agent.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { useCallback, useState } from 'react';

import type { Language } from '../types';
import AgentBalance from './AgentBalance';
import AgentProfile from './AgentProfile';
import AgentStats from './AgentStats';
import Chat from './Chat';
import Footer from './Footer';
import Navbar from './Navbar';
import Stream from './Stream';

export default function Agent() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isMobileChatOpen, setIsMobileChatOpen] = useState(false);
const [currentLanguage, setCurrentLanguage] = useState<Language>('en');

const handleLanguageChange = useCallback((lang: Language) => {
Expand All @@ -20,6 +21,8 @@ export default function Agent() {
<Navbar
isMobileMenuOpen={isMobileMenuOpen}
setIsMobileMenuOpen={setIsMobileMenuOpen}
isMobileChatOpen={isMobileChatOpen}
setIsMobileChatOpen={setIsMobileChatOpen}
setCurrentLanguage={handleLanguageChange}
currentLanguage={currentLanguage}
/>
Expand All @@ -29,13 +32,28 @@ export default function Agent() {
className={`
${
isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'
} fixed z-20 flex h-full w-full flex-col overflow-y-auto bg-black p-2 transition-transform duration-300 lg:relative lg:z-0 lg:w-1/3 lg:translate-x-0 lg:border-[#5788FA]/50 lg:border-r `}
} fixed z-20 flex h-full w-full flex-col overflow-y-auto bg-black transition-transform duration-300 lg:relative lg:z-0 lg:w-1/3 lg:translate-x-0 lg:border-[#5788FA]/50 lg:border-r `}
>
<AgentProfile currentLanguage={currentLanguage} />
<AgentStats currentLanguage={currentLanguage} />
<AgentBalance />
</div>

<div className="flex w-full lg:w-2/3">
<Stream currentLanguage={currentLanguage} />
<Chat currentLanguage={currentLanguage} className="hidden" />
</div>

<Stream currentLanguage={currentLanguage} />
<div
className={`
${
isMobileChatOpen ? 'translate-y-0' : 'translate-x-full'
} fixed top-0 z-8 flex h-full w-full flex-col overflow-y-auto bg-black pt-[100px] transition-transform duration-300 md:hidden`}
>
<Chat
currentLanguage={currentLanguage}
className="flex w-full flex-col"
/>
</div>
</div>
<Footer />
</div>
Expand Down
63 changes: 63 additions & 0 deletions app/components/AgentAssets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useCallback, useMemo, useState } from 'react';

// TODO: add assets
export default function AgentAssets() {
const [tab, setTab] = useState('tokens');

const tokensClass = useMemo(() => {
if (tab === 'tokens') {
return 'border-b border-[#5788FA] flex items-center justify-center py-1';
}
return ' flex items-center justify-center py-1';
}, [tab]);

const nftsClass = useMemo(() => {
if (tab === 'nft') {
return 'border-b border-[#5788FA] flex items-center justify-center py-1';
}
return ' flex items-center justify-center py-1';
}, [tab]);

const createdClass = useMemo(() => {
if (tab === 'created') {
return 'border-b border-[#5788FA] flex items-center justify-center py-1';
}
return ' flex items-center justify-center py-1';
}, [tab]);

const handleTabChange = useCallback((tab: string) => {
return () => setTab(tab);
}, []);

return (
<div className="mr-2 mb-4 rounded-sm bg-black p-4">
<div className="flex flex-col items-start gap-4">
<div className="flex w-full grow gap-6 border-zinc-700 border-b">
<button
type="button"
onClick={handleTabChange('tokens')}
className={tokensClass}
>
Tokens
</button>
<button
type="button"
onClick={handleTabChange('nft')}
className={nftsClass}
>
NFTs
</button>
<button
type="button"
onClick={handleTabChange('created')}
className={createdClass}
>
Created
</button>
</div>

{tab === 'tokens' ? <div>tokens</div> : <div>nfts</div>}
</div>
</div>
);
}
19 changes: 19 additions & 0 deletions app/components/AgentBalance.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useBalance } from 'wagmi';
import { AGENT_WALLET_ADDRESS } from '../constants';

export default function AgentBalance() {
const { data } = useBalance({
address: AGENT_WALLET_ADDRESS,
query: { refetchInterval: 5000 },
});

return (
<div className="rounded-sm border-zinc-700 border-t bg-black p-4 pt-8">
<div className="flex flex-col items-start ">
<span className="font-bold text-3xl text-[#5788FA]">
{`${Number.parseFloat(data?.formatted || '').toFixed(6)} ETH`}
</span>
</div>
</div>
);
}
11 changes: 7 additions & 4 deletions app/components/AgentProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { AGENT_NAME, AGENT_WALLET_ADDRESS, notoSansThai } from '../constants';
import { translations } from '../translations';
import type { Language } from '../types';

type AgentProfileProps = {
Expand All @@ -17,7 +16,7 @@ export default function AgentProfile({ currentLanguage }: AgentProfileProps) {
.writeText(AGENT_WALLET_ADDRESS)
.then(() => {
setShowToast(true);
setTimeout(() => setShowToast(false), 2000); // Hide toast after 2 seconds
setTimeout(() => setShowToast(false), 2000);
})
.catch((err) => {
console.error('Failed to copy wallet address: ', err);
Expand Down Expand Up @@ -59,7 +58,7 @@ export default function AgentProfile({ currentLanguage }: AgentProfileProps) {
}, []);

return (
<div className="mb-4">
<div className="p-4">
<div className="flex flex-col space-y-4 py-2">
<div className="flex items-center space-x-5">
<svg
Expand Down Expand Up @@ -99,12 +98,16 @@ export default function AgentProfile({ currentLanguage }: AgentProfileProps) {
</div>
</div>

{/* TODO: update description */}
<p
className={`text-[#5788FA] text-base ${
currentLanguage === 'th' ? notoSansThai.className : ''
}`}
>
{translations[currentLanguage].profile.bio}
Lorem Ipsum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industrys standard dummy text ever
since the 1500s, when an unknown printer took a galley of type and
scrambled it to make a type specimen book.
</p>
</div>
</div>
Expand Down
76 changes: 0 additions & 76 deletions app/components/AgentStats.tsx

This file was deleted.

114 changes: 114 additions & 0 deletions app/components/Chat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { cn } from '@coinbase/onchainkit/theme';
import { useCallback, useEffect, useRef, useState } from 'react';
import { notoSansThai } from '../constants';
import useChat from '../hooks/useChat';
import type { AgentMessage, Language, StreamEntry } from '../types';
import ChatInput from './ChatInput';
import StreamItem from './StreamItem';

type ChatProps = {
currentLanguage: Language;
enableLiveStream?: boolean;
className?: string;
};

export default function Chat({ className, currentLanguage }: ChatProps) {
const [userInput, setUserInput] = useState('');
const [streamEntries, setStreamEntries] = useState<StreamEntry[]>([]);

const bottomRef = useRef<HTMLDivElement>(null);

// TODO: revisit this logic
const handleSuccess = useCallback((messages: AgentMessage[]) => {
// const message = messages.find((res) => res.event === "agent");
const filteredMessages = messages.filter(
(msg) => msg.event !== 'completed',
);
const streams = filteredMessages.map((msg) => {
return {
timestamp: new Date(),
content: msg?.data || '',
type: msg?.event,
};
});
// const streamEntry = {
// timestamp: new Date(),
// content: message?.data || "",
// };
setStreamEntries((prev) => [...prev, ...streams]);
}, []);

const { postChat, isLoading } = useChat({ onSuccess: handleSuccess });

const handleSubmit = useCallback(
async (e: React.FormEvent) => {
e.preventDefault();
if (!userInput.trim()) {
return;
}

setUserInput('');

const userMessage: StreamEntry = {
timestamp: new Date(),
type: 'user',
content: userInput.trim(),
};

setStreamEntries((prev) => [...prev, userMessage]);

postChat(userInput);
},
[postChat, userInput],
);

const handleKeyPress = useCallback(
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSubmit(e);
}
},
[handleSubmit],
);

// biome-ignore lint/correctness/useExhaustiveDependencies: Dependency is required
useEffect(() => {
// scrolls to the bottom of the chat when messages change
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [streamEntries]);

return (
<div className={cn('flex h-full w-1/2 grow flex-col md:flex', className)}>
<div className="flex grow flex-col overflow-y-auto p-4 pb-20">
<p
className={`text-zinc-500 ${
currentLanguage === 'th' ? notoSansThai.className : ''
}`}
>
Ask me something...
</p>
<div className="mt-4 space-y-2" role="log" aria-live="polite">
{streamEntries.map((entry, index) => (
<StreamItem
key={`${entry.timestamp.toDateString()}-${index}`}
entry={entry}
currentLanguage={currentLanguage}
/>
))}
</div>

<div className="mt-3" ref={bottomRef} />
</div>

<ChatInput
currentLanguage={currentLanguage}
userInput={userInput}
handleKeyPress={handleKeyPress}
handleSubmit={handleSubmit}
setUserInput={setUserInput}
disabled={isLoading}
/>
</div>
);
}
Loading

0 comments on commit 851032d

Please sign in to comment.