1
0

agent-details.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. // "use client";
  2. import type { Agent } from "@/types/agent";
  3. import VRMDemo from "./vrm-demo";
  4. import { PriceChart } from "./price-chart";
  5. import { TokenData } from "./token-data";
  6. import { AgentDescription } from "./agent-description";
  7. import { SocialMediaButtons } from "./social-media-buttons";
  8. import { AgentTags } from "./agent-tags";
  9. import { AgentTiers } from "./agent-tiers";
  10. import { Button } from "./ui/button";
  11. import { MessageSquare, ArrowRightLeft } from "lucide-react";
  12. import { Integrations } from "./integrations";
  13. import { useEffect, useState } from "react";
  14. import { useTokens } from "@/hooks/use-token";
  15. import { AlertTriangle } from "lucide-react";
  16. import { ConnectButton } from "@rainbow-me/rainbowkit";
  17. import { useReserveToken } from "@/hooks/use-reserve-token";
  18. import { useAccount, useReadContract } from "wagmi";
  19. import { ERC721_ABI } from "@/utils/abi/erc721";
  20. import { formatUnits } from "ethers";
  21. import { ERC20_ABI } from "@/utils/abi/erc20";
  22. import { AgentVrmDiagnosis } from "./agent-diagnosis";
  23. import { AgentDemo } from "./agent-demo";
  24. interface AgentDetailsProps {
  25. agent: Agent;
  26. }
  27. const AMICA_URL = process.env.NEXT_PUBLIC_AMICA_URL as string;
  28. const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
  29. export function AgentDetails({ agent }: AgentDetailsProps) {
  30. const [diagnosisPassed, setDiagnosisPassed] = useState(false);
  31. const [reserveAmount, setReserveAmount] = useState("");
  32. const [talentShow, setTalentShow] = useState(false);
  33. const [talentRunning, setTalentRunning] = useState(false);
  34. const { isConnected, address } = useAccount();
  35. const { stats, priceHistory, tokenAddress, loading, error } = useTokens(Number(agent.id));
  36. const { write: reserveTokens, isLoading: reserving, isSuccess: reserveSuccess, error: reserveError } = useReserveToken();
  37. const { data, isLoading: loadingAius, refetch: refetchAiusData } = useReadContract({
  38. address: process.env.NEXT_PUBLIC_CONTRACT_ADDRESS! as `0x${string}`,
  39. abi: ERC721_ABI,
  40. functionName: "getAiusAndOwed",
  41. args: [BigInt(agent.id), address],
  42. query: { enabled: isConnected && !!address },
  43. });
  44. const [aius, owed] = (data as [bigint, bigint]) || [BigInt(0), BigInt(0)];
  45. const { data: aiusAmount, refetch: refetchAiusAmount } = useReadContract({
  46. address: process.env.NEXT_PUBLIC_AIUS_CONTRACT_ADDRESS! as `0x${string}`,
  47. abi: ERC20_ABI,
  48. functionName: "balanceOf",
  49. args: [address],
  50. query: { enabled: isConnected && !!address },
  51. });
  52. const formattedAius = aius ? formatUnits(aius, 18) : "0.0";
  53. const formattedOwed = owed ? formatUnits(owed, 18) : "0.0";
  54. const isPairNotCreated = error == "Pair not created";
  55. // refetch aius and reserve amount after reserve, reload page if pair created
  56. useEffect(() => {
  57. if (reserveSuccess) {
  58. const refetchAndCheck = async () => {
  59. const { data: aiusAndOwedData } = await refetchAiusData();
  60. await refetchAiusAmount();
  61. setReserveAmount("");
  62. const [newAius, newOwed] = (aiusAndOwedData as [bigint, bigint]) || [BigInt(0), BigInt(0)];
  63. const formattedAius = parseFloat(formatUnits(newAius, 18));
  64. if (formattedAius >= 100) {
  65. await delay(5000);
  66. window.location.reload(); // Refresh the page
  67. }
  68. };
  69. refetchAndCheck();
  70. }
  71. }, [refetchAiusData, reserveSuccess, refetchAiusAmount]);
  72. if (loading) {
  73. return (
  74. <div className="sticky top-0 z-20 bg-white/80 backdrop-blur-sm border-b">
  75. <div className="flex justify-center items-center p-8">
  76. <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900"></div>
  77. </div>
  78. </div>
  79. );
  80. }
  81. if (error && !isPairNotCreated) {
  82. return (
  83. <div className="sticky top-0 z-20 bg-white/80 backdrop-blur-sm border-b">
  84. <div className="p-4 text-red-500">
  85. Error loading agents: {typeof error === "object" && error !== null && "message" in error ? (error as Error).message : String(error)}
  86. </div>
  87. </div>
  88. );
  89. }
  90. return (
  91. <div className="min-h-screen bg-white text-gray-800">
  92. <div className="container mx-auto px-4 py-12">
  93. <div className="absolute top-4 left-4 z-30">
  94. <Button
  95. variant="ghost"
  96. className="text-gray-900 hover:text-gray-500 font-orbitron font-bold text-xl"
  97. onClick={() => window.location.href = "/"} // Replace with your actual route
  98. >
  99. <ArrowRightLeft className="mr-2 h-4 w-4" />
  100. Back to Village
  101. </Button>
  102. </div>
  103. <h1 className="text-5xl font-orbitron font-bold text-gray-900 mb-2 text-center">
  104. {agent.name}
  105. </h1>
  106. <div className="absolute top-4 right-4">
  107. <ConnectButton />
  108. </div>
  109. <p className="text-center text-gray-500 mb-2 font-roboto-mono">
  110. {agent.token} | {agent.tier?.name} (Level {agent.tier?.level})
  111. </p>
  112. {isPairNotCreated && (
  113. <div className="p-6 bg-yellow-100 text-yellow-700 rounded-lg flex items-center">
  114. <AlertTriangle className="mr-2" />
  115. Pair not created yet.
  116. </div>
  117. )}
  118. {!isPairNotCreated && (<TokenData stats={stats} />)}
  119. <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 mt-12">
  120. <div className="space-y-12">
  121. <AgentDemo agent={agent} talentShow={talentShow} setTalentShow={setTalentShow} talentRunning={talentRunning} setTalentRunning={setTalentRunning} />
  122. {!isPairNotCreated && (<PriceChart priceHistory={isPairNotCreated ? [] : priceHistory} />)}
  123. <AgentVrmDiagnosis agent={agent} index={Number(agent.id)} setDiagnosisIsPassed={setDiagnosisPassed} />
  124. </div>
  125. <div className="space-y-8">
  126. <div className="flex justify-center space-x-4 mb-8">
  127. <Button
  128. className="bg-blue-500 hover:bg-blue-600 text-white font-roboto-mono"
  129. onClick={() =>
  130. window.open(`${AMICA_URL}/agent/${agent.agentId}`, "_blank", "noopener,noreferrer")
  131. }
  132. disabled={!diagnosisPassed}
  133. title={!diagnosisPassed ? "Chat is disabled: Agent is inactive." : ""}
  134. >
  135. <MessageSquare className="mr-2 h-4 w-4" /> Chat
  136. </Button>
  137. <Button
  138. className="bg-red-500 hover:bg-red-600 text-white font-roboto-mono"
  139. onClick={() => setTalentShow(true)}
  140. disabled={!diagnosisPassed || talentRunning}
  141. title={!diagnosisPassed ? "Talent show is disabled: Agent is inactive." : ""}
  142. >
  143. <MessageSquare className="mr-2 h-4 w-4" /> Talent Show
  144. </Button>
  145. {isPairNotCreated ? (
  146. <div className={isConnected ? "w-full max-w-sm space-y-4 p-4 border rounded-2xl shadow-md bg-white" : ""}>
  147. {isConnected && (
  148. <>
  149. <label className="block text-sm font-medium text-gray-700 font-roboto-mono mb-1">
  150. Your AIUS balance : {Number(formatUnits(aiusAmount as bigint, 18)).toFixed(2)}
  151. </label>
  152. <div className="w-full max-w-sm p-4 mb-4 bg-gray-50 border rounded-xl shadow-sm text-sm text-gray-700 font-roboto-mono">
  153. <div className="flex justify-between">
  154. <span className="font-semibold">AIUS Deposited on token:</span>
  155. <span className="text-blue-600">{formattedAius ? formattedAius.toString() : loadingAius ? "Loading..." : "0"}</span>
  156. </div>
  157. <div className="flex justify-between mt-1">
  158. <span className="font-semibold">Your Own AINFT:</span>
  159. <span className="text-green-600">{formattedOwed ? formattedOwed.toString() : loadingAius ? "Loading..." : "0"}</span>
  160. </div>
  161. </div>
  162. <div className="relative">
  163. <input
  164. type="number"
  165. min="0.1"
  166. max={100 - Number(formattedAius)}
  167. step="0.1"
  168. value={reserveAmount}
  169. onChange={(e) => {
  170. const inputValue = parseFloat(e.target.value);
  171. const maxLimit = 100 - Number(formattedAius);
  172. if (!isNaN(inputValue) && inputValue <= maxLimit) {
  173. setReserveAmount(e.target.value);
  174. } else if (e.target.value === "") {
  175. setReserveAmount("");
  176. }
  177. }}
  178. placeholder={`${100 - Number(formattedAius)} AIUS before added liquidity`}
  179. className="w-full pl-4 pr-16 py-3 text-sm border rounded-xl shadow-sm font-roboto-mono focus:outline-none focus:ring-2 focus:ring-yellow-400"
  180. />
  181. <span className="absolute right-4 top-1/2 transform -translate-y-1/2 text-gray-400 font-semibold text-sm">
  182. AIUS
  183. </span>
  184. </div>
  185. </>
  186. )}
  187. <Button
  188. className="w-full bg-yellow-500 hover:bg-yellow-600 text-white font-roboto-mono py-3 rounded-xl"
  189. onClick={() => {
  190. if (!isConnected) {
  191. alert("Please connect your wallet first.");
  192. return;
  193. }
  194. reserveTokens(Number(agent.id), reserveAmount);
  195. }}
  196. disabled={reserving || 100 - Number(formattedAius) == 0}
  197. >
  198. {reserving ? "Reserving..." : (
  199. <>
  200. <AlertTriangle className="mr-2 h-4 w-4" />
  201. Reserve Token
  202. </>
  203. )}
  204. </Button>
  205. {reserveError && !reserveError?.message?.toLowerCase().includes("user rejected") && (
  206. <div className="text-red-600 text-sm font-roboto-mono">{reserveError.message}</div>
  207. )}
  208. </div>
  209. ) : (
  210. <Button
  211. className="bg-pink-500 hover:bg-pink-600 text-white font-roboto-mono"
  212. onClick={() =>
  213. window.open(
  214. `https://app.uniswap.org/#/swap?inputCurrency=${tokenAddress}`,
  215. "_blank",
  216. "noopener,noreferrer"
  217. )
  218. }
  219. >
  220. <ArrowRightLeft className="mr-2 h-4 w-4" /> Buy/Sell on Uniswap
  221. </Button>
  222. )}
  223. </div>
  224. <AgentDescription description={agent.description} />
  225. <SocialMediaButtons agent={agent} />
  226. {agent.tags.length > 1 &&
  227. <AgentTags tags={agent.tags} />}
  228. <AgentTiers currentTier={agent.tier!} />
  229. <Integrations integrations={agent.integrations} />
  230. </div>
  231. </div>
  232. </div>
  233. </div >
  234. );
  235. }