Skip to content

Commit

Permalink
chore: update streams and icons (#24)
Browse files Browse the repository at this point in the history
* update streams and icons

* fix format

* reorganize components

* fix imports

* fix lint

* fix lint

* adjust mobile style

* fix format

* refactor

---------

Co-authored-by: Alissa Crane <[email protected]>
  • Loading branch information
abcrane123 and alissacrane-cb authored Nov 6, 2024
1 parent ac99778 commit 2359048
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 322 deletions.
119 changes: 6 additions & 113 deletions app/components/Agent.tsx
Original file line number Diff line number Diff line change
@@ -1,148 +1,41 @@
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useState } from 'react';

import useChat from '../hooks/useChat';
import type {
ActionEntry,
AgentMessage,
Language,
StreamEntry,
} from '../types';
import type { Language } from '../types';
import AgentProfile from './AgentProfile';
import AgentStats from './AgentStats';
import ChatInput from './ChatInput';
import Footer from './Footer';
import Navbar from './Navbar';
import Stream from './Stream';

export default function Agent() {
const [streamEntries, setStreamEntries] = useState<StreamEntry[]>([]);
const [userInput, setUserInput] = useState('');
const [isThinking, setIsThinking] = useState(true);
const [loadingDots, setLoadingDots] = useState('');
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [currentLanguage, setCurrentLanguage] = useState<Language>('en');
const [isLiveDotVisible, setIsLiveDotVisible] = useState(true);
const [isChatMode, setIsChatMode] = useState(false);

const handleSuccess = useCallback((messages: AgentMessage[]) => {
const message = messages.find((res) => res.event === 'agent');
const streamEntry = {
timestamp: new Date(),
content: message?.data || '',
};
setIsThinking(false);
setStreamEntries((prev) => [...prev, streamEntry]);
setTimeout(() => {
setIsThinking(true);
}, 800);
}, []);

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

// enables live stream of agent thoughts
useEffect(() => {
const streamInterval = setInterval(() => {
if (!isLoading && !isChatMode) {
postChat('same a one liner that is inspiring');
}
}, 1500);

return () => {
clearInterval(streamInterval);
};
}, [isLoading, postChat, isChatMode]);

// enables dot animation for "agent is thinking..."
useEffect(() => {
const dotsInterval = setInterval(() => {
setLoadingDots((prev) => (prev.length >= 3 ? '' : `${prev}.`));
}, 500);

return () => clearInterval(dotsInterval);
}, []);

// enables glowing live on sepolia dot
useEffect(() => {
const dotInterval = setInterval(() => {
setIsLiveDotVisible((prev) => !prev);
}, 1000);

return () => clearInterval(dotInterval);
}, []);

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

// disable live stream
setIsChatMode(true);
setUserInput('');

const userMessage: ActionEntry = {
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],
);

const handleLanguageChange = useCallback((lang: Language) => {
setCurrentLanguage(lang);
setStreamEntries([]);
}, []);

return (
<div className="relative flex h-screen flex-col overflow-hidden bg-black font-mono text-[#5788FA]">
<Navbar
isMobileMenuOpen={isMobileMenuOpen}
setIsMobileMenuOpen={setIsMobileMenuOpen}
isLiveDotVisible={isLiveDotVisible}
setCurrentLanguage={handleLanguageChange}
currentLanguage={currentLanguage}
/>

<div className="relative flex flex-grow overflow-hidden">
<div
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 `}
${
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 `}
>
<AgentProfile currentLanguage={currentLanguage} />
<AgentStats currentLanguage={currentLanguage} />
</div>

<div className="flex w-full flex-grow flex-col lg:w-2/3">
<Stream
currentLanguage={currentLanguage}
streamEntries={streamEntries}
isThinking={isThinking && !isChatMode}
loadingDots={loadingDots}
/>
<ChatInput
currentLanguage={currentLanguage}
userInput={userInput}
handleKeyPress={handleKeyPress}
handleSubmit={handleSubmit}
setUserInput={setUserInput}
/>
</div>
<Stream currentLanguage={currentLanguage} />
</div>
<Footer />
</div>
Expand Down
88 changes: 49 additions & 39 deletions app/components/AgentStats.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
import { useBalance } from 'wagmi';
import { useBalance, useTransactionCount } from 'wagmi';
import { AGENT_WALLET_ADDRESS, notoSansThai } from '../constants';
import { translations } from '../translations';
import type { Language } from '../types';

type AgentStats = {
type AgentStatsItemProps = {
currentLanguage: Language;
label: string;
value: string | number;
};

const dummyStats = {
earned: 10000,
spent: 4000,
nftsOwned: 3,
tokensOwned: 0,
transactions: 0,
thoughts: 900,
function AgentStatsItem({
currentLanguage,
label,
value,
}: AgentStatsItemProps) {
return (
<li className={currentLanguage === 'th' ? notoSansThai.className : ''}>
{`${label}: ${value}`}
</li>
);
}

type AgentStats = {
currentLanguage: Language;
};

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

const { data: transactionCount } = useTransactionCount({
address: AGENT_WALLET_ADDRESS,
query: { refetchInterval: 5000 },
});

return (
<div className="mr-2 mb-4 rounded-sm border border-[#5788FA]/50 bg-black">
<div className="flex flex-col items-start p-4">
Expand All @@ -29,36 +44,31 @@ export default function AgentStats({ currentLanguage }: AgentStats) {
</span>
{/* TODO: update with actual data */}
<ul className="space-y-1 pt-4">
<li
className={currentLanguage === 'th' ? notoSansThai.className : ''}
>
{translations[currentLanguage].profile.stats.earned}: $
{dummyStats.earned.toFixed(2)}
</li>
<li
className={currentLanguage === 'th' ? notoSansThai.className : ''}
>
{translations[currentLanguage].profile.stats.spent}: $
{dummyStats.spent.toFixed(2)}
</li>
<li
className={currentLanguage === 'th' ? notoSansThai.className : ''}
>
{translations[currentLanguage].profile.stats.nfts}:{' '}
{dummyStats.nftsOwned}
</li>
<li
className={currentLanguage === 'th' ? notoSansThai.className : ''}
>
{translations[currentLanguage].profile.stats.tokens}:{' '}
{dummyStats.tokensOwned}
</li>
<li
className={currentLanguage === 'th' ? notoSansThai.className : ''}
>
{translations[currentLanguage].profile.stats.transactions}:{' '}
{dummyStats.transactions}
</li>
<AgentStatsItem
currentLanguage={currentLanguage}
label={translations[currentLanguage].profile.stats.earned}
value="N/A"
/>
<AgentStatsItem
currentLanguage={currentLanguage}
label={translations[currentLanguage].profile.stats.spent}
value="N/A"
/>
<AgentStatsItem
currentLanguage={currentLanguage}
label={translations[currentLanguage].profile.stats.nfts}
value="N/A"
/>
<AgentStatsItem
currentLanguage={currentLanguage}
label={translations[currentLanguage].profile.stats.tokens}
value="N/A"
/>
<AgentStatsItem
currentLanguage={currentLanguage}
label={translations[currentLanguage].profile.stats.transactions}
value={transactionCount || 'N/A'}
/>
</ul>
</div>
</div>
Expand Down
45 changes: 29 additions & 16 deletions app/components/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function PremadeChatInput({
<button
type="submit"
onClick={() => setUserInput(text)}
className={`whitespace-nowrap rounded-sm border border-[#5788FA]/50 px-2 py-1 text-[#5788FA] transition-colors hover:bg-zinc-900 hover:text-[#3D7BFF] ${
className={`w-full whitespace-nowrap rounded-sm border border-[#5788FA]/50 px-2 py-1 text-[#5788FA] transition-colors hover:bg-zinc-900 hover:text-[#3D7BFF] lg:w-auto lg:grow ${
currentLanguage === 'th' ? notoSansThai.className : ''
}`}
>
Expand Down Expand Up @@ -54,21 +54,34 @@ export default function ChatInput({
return (
<form
onSubmit={handleSubmit}
className="flex w-full flex-col border-[#5788FA]/50 border-t bg-black p-2 pb-10 lg:pb-2"
className="flex w-full flex-col border-[#5788FA]/50 border-t bg-black p-4 pb-10 lg:pb-2"
>
<div className="flex flex-col">
<textarea
value={userInput}
onChange={handleInputChange}
onKeyPress={handleKeyPress}
className={`h-24 w-full bg-black p-4 pr-10 text-[#5788FA] placeholder-[#5788FA] placeholder-opacity-50 lg:h-36 ${
currentLanguage === 'th' ? notoSansThai.className : ''
}`}
placeholder={translations[currentLanguage].chat.placeholder}
rows={1}
/>
<div className="flex w-full items-center justify-between px-2 pt-4">
<div className="ml-2 flex space-x-2 overflow-x-auto text-xs lg:text-sm">
<div className="flex flex-col gap-2">
<div className="flex gap-2">
<textarea
value={userInput}
onChange={handleInputChange}
onKeyPress={handleKeyPress}
className={`h-24 w-full bg-black p-2 pr-10 text-[#5788FA] placeholder-[#5788FA] placeholder-opacity-50 lg:h-36 ${
currentLanguage === 'th' ? notoSansThai.className : ''
}`}
placeholder={translations[currentLanguage].chat.placeholder}
rows={1}
/>
<button
type="submit"
disabled={!/[a-zA-Z]/.test(userInput)}
className={`mt-auto rounded-sm p-1.5 transition-colors xl:hidden ${
/[a-zA-Z]/.test(userInput)
? 'bg-[#5788FA] text-zinc-950 hover:bg-[#3D7BFF]'
: 'cursor-not-allowed bg-[#5788FA] text-zinc-950 opacity-50'
}`}
>
<SendSvg />
</button>
</div>
<div className="flex w-full items-center justify-between gap-4 py-2">
<div className="flex grow flex-col gap-2 overflow-x-auto text-xs lg:flex-row lg:text-sm">
<PremadeChatInput
setUserInput={setUserInput}
currentLanguage={currentLanguage}
Expand All @@ -88,7 +101,7 @@ export default function ChatInput({
<button
type="submit"
disabled={!/[a-zA-Z]/.test(userInput)}
className={`rounded-sm p-1.5 transition-colors ${
className={`rounded-sm p-1.5 transition-colors max-xl:hidden ${
/[a-zA-Z]/.test(userInput)
? 'bg-[#5788FA] text-zinc-950 hover:bg-[#3D7BFF]'
: 'cursor-not-allowed bg-[#5788FA] text-zinc-950 opacity-50'
Expand Down
15 changes: 12 additions & 3 deletions app/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { useCallback } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { notoSansThai } from '../constants';
import { translations } from '../translations';
import type { Language } from '../types';
import LanguageSelector from './LanguageSelector';

type NavbarProps = {
setIsMobileMenuOpen: (isOpen: boolean) => void;
isLiveDotVisible: boolean;
isMobileMenuOpen: boolean;
setCurrentLanguage: (language: Language) => void;
currentLanguage: Language;
Expand All @@ -15,10 +14,20 @@ type NavbarProps = {
export default function Navbar({
setIsMobileMenuOpen,
isMobileMenuOpen,
isLiveDotVisible,
setCurrentLanguage,
currentLanguage,
}: NavbarProps) {
const [isLiveDotVisible, setIsLiveDotVisible] = useState(true);

// enables glowing live on sepolia dot
useEffect(() => {
const dotInterval = setInterval(() => {
setIsLiveDotVisible((prev) => !prev);
}, 1000);

return () => clearInterval(dotInterval);
}, []);

const handleClick = useCallback(() => {
setIsMobileMenuOpen(!isMobileMenuOpen);
}, [isMobileMenuOpen, setIsMobileMenuOpen]);
Expand Down
Loading

0 comments on commit 2359048

Please sign in to comment.