Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement redesign #27

Merged
merged 12 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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