From 367c37410a7c76970e47f3622411448fa89f3587 Mon Sep 17 00:00:00 2001 From: pluto Date: Tue, 4 Mar 2025 19:47:41 +0900 Subject: [PATCH 1/2] Update basic layout --- app/globals.css | 7 +- app/layout.tsx | 66 +++-- app/page.tsx | 90 ++----- components/ChatMessageBubble.tsx | 100 +++---- components/ChatWindow.tsx | 450 +++++++++++++++---------------- 5 files changed, 329 insertions(+), 384 deletions(-) diff --git a/app/globals.css b/app/globals.css index bbdc80a..19d3c8c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -3,17 +3,18 @@ @tailwind utilities; body { - color: #f8f8f8; - background: #131318; + color: #161514; + background: #fff; } body input, body textarea { color: black; + border: 10px solid #d16e1033; } a { - color: #2d7bd4; + color: #1d1bd1; } a:hover { diff --git a/app/layout.tsx b/app/layout.tsx index 0b627ec..86949a4 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,42 +4,36 @@ import { Public_Sans } from "next/font/google"; const publicSans = Public_Sans({ subsets: ["latin"] }); export default function RootLayout({ - children, + children, }: { - children: React.ReactNode; + children: React.ReactNode; }) { - return ( - - - SolanaAgentKit + LangChain + Next.js Template - - - - - - - - - - - -
{children}
- - - ); + return ( + + + Pie.Fun Super Smart AI Agent + + + + + + + + + + + +
{children}
+ + + ); } diff --git a/app/page.tsx b/app/page.tsx index 1578c72..68cec70 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,75 +1,23 @@ import { ChatWindow } from "@/components/ChatWindow"; export default function Home() { - const InfoCard = ( -
-

- SolanaAgentKit + LangChain.js 🦜🔗 + Next.js -

- -
- ); - return ( - - ); + const InfoCard = ( +
+

+ Pie.Fun Super Smart AI Agent +

+

+ Ask me anything about Pie.Fun. I'm your friendly Pie.Fun agent! +

+
+ ); + return ( + + ); } diff --git a/components/ChatMessageBubble.tsx b/components/ChatMessageBubble.tsx index aef1231..bbf1df6 100644 --- a/components/ChatMessageBubble.tsx +++ b/components/ChatMessageBubble.tsx @@ -3,56 +3,58 @@ import type { Message } from "ai/react"; import { useMemo } from "react"; export function ChatMessageBubble(props: { - message: Message; - aiEmoji?: string; - sources: any[]; + message: Message; + aiEmoji?: string; + sources: any[]; }) { - const colorClassName = - props.message.role === "user" ? "bg-sky-600" : "bg-slate-50 text-black"; - const alignmentClassName = - props.message.role === "user" ? "ml-auto" : "mr-auto"; - const prefix = props.message.role === "user" ? "🧑" : props.aiEmoji; + const colorClassName = + props.message.role === "user" + ? "bg-[#D16E1033]" + : "bg-[#ECEDF1] text-black"; + const alignmentClassName = + props.message.role === "user" ? "ml-auto" : "mr-auto"; + const prefix = props.message.role === "user" ? "🧑" : props.aiEmoji; - const content = useMemo(() => { - return markdownToHtml(props.message.content); - }, [props.message.content]); + const content = useMemo(() => { + return markdownToHtml(props.message.content); + }, [props.message.content]); - return ( -
-
{prefix}
-
-
- {props.sources && props.sources.length ? ( - <> - -

🔍 Sources:

-
- - {props.sources?.map((source, i) => ( -
- {i + 1}. "{source.pageContent}" - {source.metadata?.loc?.lines !== undefined ? ( -
-
- Lines {source.metadata?.loc?.lines?.from} to{" "} - {source.metadata?.loc?.lines?.to} -
- ) : ( - "" - )} -
- ))} -
- - ) : ( - "" - )} -
-
- ); + return ( +
+
{prefix}
+
+
+ {props.sources && props.sources.length ? ( + <> + +

🔍 Sources:

+
+ + {props.sources?.map((source, i) => ( +
+ {i + 1}. "{source.pageContent}" + {source.metadata?.loc?.lines !== undefined ? ( +
+
+ Lines {source.metadata?.loc?.lines?.from} to{" "} + {source.metadata?.loc?.lines?.to} +
+ ) : ( + "" + )} +
+ ))} +
+ + ) : ( + "" + )} +
+
+ ); } diff --git a/components/ChatWindow.tsx b/components/ChatWindow.tsx index b1f0d90..3351a0b 100644 --- a/components/ChatWindow.tsx +++ b/components/ChatWindow.tsx @@ -12,237 +12,237 @@ import { ChatMessageBubble } from "@/components/ChatMessageBubble"; import { IntermediateStep } from "./IntermediateStep"; export function ChatWindow(props: { - endpoint: string; - emptyStateComponent: ReactElement; - placeholder?: string; - titleText?: string; - emoji?: string; - showIntermediateStepsToggle?: boolean; + endpoint: string; + emptyStateComponent: ReactElement; + placeholder?: string; + titleText?: string; + emoji?: string; + showIntermediateStepsToggle?: boolean; }) { - const messageContainerRef = useRef(null); + const messageContainerRef = useRef(null); - const { - endpoint, - emptyStateComponent, - placeholder, - titleText = "An LLM", - showIntermediateStepsToggle, - emoji, - } = props; + const { + endpoint, + emptyStateComponent, + placeholder, + titleText = "An LLM", + showIntermediateStepsToggle, + emoji, + } = props; - const [showIntermediateSteps, setShowIntermediateSteps] = useState(false); - const [intermediateStepsLoading, setIntermediateStepsLoading] = - useState(false); - const intemediateStepsToggle = showIntermediateStepsToggle && ( -
- setShowIntermediateSteps(e.target.checked)} - > - -
- ); + const [showIntermediateSteps, setShowIntermediateSteps] = useState(false); + const [intermediateStepsLoading, setIntermediateStepsLoading] = + useState(false); + const intemediateStepsToggle = showIntermediateStepsToggle && ( +
+ setShowIntermediateSteps(e.target.checked)} + > + +
+ ); - const [sourcesForMessages, setSourcesForMessages] = useState< - Record - >({}); + const [sourcesForMessages, setSourcesForMessages] = useState< + Record + >({}); - const { - messages, - input, - setInput, - handleInputChange, - handleSubmit, - isLoading: chatEndpointIsLoading, - setMessages, - } = useChat({ - api: endpoint, - onResponse(response) { - const sourcesHeader = response.headers.get("x-sources"); - const sources = sourcesHeader - ? JSON.parse(Buffer.from(sourcesHeader, "base64").toString("utf8")) - : []; - const messageIndexHeader = response.headers.get("x-message-index"); - if (sources.length && messageIndexHeader !== null) { - setSourcesForMessages({ - ...sourcesForMessages, - [messageIndexHeader]: sources, - }); - } - }, - streamMode: "text", - onError: (e) => { - toast(e.message, { - theme: "dark", - }); - }, - }); + const { + messages, + input, + setInput, + handleInputChange, + handleSubmit, + isLoading: chatEndpointIsLoading, + setMessages, + } = useChat({ + api: endpoint, + onResponse(response) { + const sourcesHeader = response.headers.get("x-sources"); + const sources = sourcesHeader + ? JSON.parse(Buffer.from(sourcesHeader, "base64").toString("utf8")) + : []; + const messageIndexHeader = response.headers.get("x-message-index"); + if (sources.length && messageIndexHeader !== null) { + setSourcesForMessages({ + ...sourcesForMessages, + [messageIndexHeader]: sources, + }); + } + }, + streamMode: "text", + onError: (e) => { + toast(e.message, { + theme: "dark", + }); + }, + }); - async function sendMessage(e: FormEvent) { - e.preventDefault(); - if (messageContainerRef.current) { - messageContainerRef.current.classList.add("grow"); - } - if (!messages.length) { - await new Promise((resolve) => setTimeout(resolve, 300)); - } - if (chatEndpointIsLoading ?? intermediateStepsLoading) { - return; - } - if (!showIntermediateSteps) { - handleSubmit(e); - // Some extra work to show intermediate steps properly - } else { - setIntermediateStepsLoading(true); - setInput(""); - const messagesWithUserReply = messages.concat({ - id: messages.length.toString(), - content: input, - role: "user", - }); - setMessages(messagesWithUserReply); - const response = await fetch(endpoint, { - method: "POST", - body: JSON.stringify({ - messages: messagesWithUserReply, - show_intermediate_steps: true, - }), - }); - const json = await response.json(); - setIntermediateStepsLoading(false); - if (response.status === 200) { - const responseMessages: Message[] = json.messages; - // Represent intermediate steps as system messages for display purposes - // TODO: Add proper support for tool messages - const toolCallMessages = responseMessages.filter( - (responseMessage: Message) => { - return ( - (responseMessage.role === "assistant" && - !!responseMessage.tool_calls?.length) || - responseMessage.role === "tool" - ); - }, - ); - const intermediateStepMessages = []; - for (let i = 0; i < toolCallMessages.length; i += 2) { - const aiMessage = toolCallMessages[i]; - const toolMessage = toolCallMessages[i + 1]; - intermediateStepMessages.push({ - id: (messagesWithUserReply.length + i / 2).toString(), - role: "system" as const, - content: JSON.stringify({ - action: aiMessage.tool_calls?.[0], - observation: toolMessage.content, - }), - }); - } - const newMessages = messagesWithUserReply; - for (const message of intermediateStepMessages) { - newMessages.push(message); - setMessages([...newMessages]); - await new Promise((resolve) => - setTimeout(resolve, 1000 + Math.random() * 1000), - ); - } - setMessages([ - ...newMessages, - { - id: newMessages.length.toString(), - content: responseMessages[responseMessages.length - 1].content, - role: "assistant", - }, - ]); - } else { - if (json.error) { - toast(json.error, { - theme: "dark", - }); - throw new Error(json.error); - } - } - } - } + async function sendMessage(e: FormEvent) { + e.preventDefault(); + if (messageContainerRef.current) { + messageContainerRef.current.classList.add("grow"); + } + if (!messages.length) { + await new Promise((resolve) => setTimeout(resolve, 300)); + } + if (chatEndpointIsLoading ?? intermediateStepsLoading) { + return; + } + if (!showIntermediateSteps) { + handleSubmit(e); + // Some extra work to show intermediate steps properly + } else { + setIntermediateStepsLoading(true); + setInput(""); + const messagesWithUserReply = messages.concat({ + id: messages.length.toString(), + content: input, + role: "user", + }); + setMessages(messagesWithUserReply); + const response = await fetch(endpoint, { + method: "POST", + body: JSON.stringify({ + messages: messagesWithUserReply, + show_intermediate_steps: true, + }), + }); + const json = await response.json(); + setIntermediateStepsLoading(false); + if (response.status === 200) { + const responseMessages: Message[] = json.messages; + // Represent intermediate steps as system messages for display purposes + // TODO: Add proper support for tool messages + const toolCallMessages = responseMessages.filter( + (responseMessage: Message) => { + return ( + (responseMessage.role === "assistant" && + !!responseMessage.tool_calls?.length) || + responseMessage.role === "tool" + ); + }, + ); + const intermediateStepMessages = []; + for (let i = 0; i < toolCallMessages.length; i += 2) { + const aiMessage = toolCallMessages[i]; + const toolMessage = toolCallMessages[i + 1]; + intermediateStepMessages.push({ + id: (messagesWithUserReply.length + i / 2).toString(), + role: "system" as const, + content: JSON.stringify({ + action: aiMessage.tool_calls?.[0], + observation: toolMessage.content, + }), + }); + } + const newMessages = messagesWithUserReply; + for (const message of intermediateStepMessages) { + newMessages.push(message); + setMessages([...newMessages]); + await new Promise((resolve) => + setTimeout(resolve, 1000 + Math.random() * 1000), + ); + } + setMessages([ + ...newMessages, + { + id: newMessages.length.toString(), + content: responseMessages[responseMessages.length - 1].content, + role: "assistant", + }, + ]); + } else { + if (json.error) { + toast(json.error, { + theme: "dark", + }); + throw new Error(json.error); + } + } + } + } - return ( -
0 ? "border" : ""}`} - > -

0 ? "" : "hidden"} text-2xl`}> - {emoji} {titleText} -

- {messages.length === 0 ? emptyStateComponent : ""} -
- {messages.length > 0 - ? [...messages].reverse().map((m, i) => { - const sourceKey = (messages.length - 1 - i).toString(); - return m.role === "system" ? ( - - ) : ( - - ); - }) - : ""} -
+ return ( +
0 ? "border" : ""}`} + > +

0 ? "" : "hidden"} text-2xl`}> + {emoji} {titleText} +

+ {messages.length === 0 ? emptyStateComponent : ""} +
+ {messages.length > 0 + ? [...messages].reverse().map((m, i) => { + const sourceKey = (messages.length - 1 - i).toString(); + return m.role === "system" ? ( + + ) : ( + + ); + }) + : ""} +
-
-
{intemediateStepsToggle}
-
- - -
-
- -
- ); +
+
{intemediateStepsToggle}
+
+ + +
+
+ +
+ ); } From 707bfd8f2f97c2f774b8f83b45e03b89d1f57f46 Mon Sep 17 00:00:00 2001 From: pluto Date: Tue, 4 Mar 2025 19:49:17 +0900 Subject: [PATCH 2/2] add simple functions --- src/agent/index.ts | 10 ++++ src/langchain/index.ts | 5 ++ src/langchain/piefun/index.ts | 2 + src/langchain/piefun/list_all_baskets.ts | 25 +++++++++ .../list_top_solana_mindshare_tokens.ts | 25 +++++++++ src/tools/piefun/index.ts | 2 + src/tools/piefun/list_all_baskets.ts | 7 +++ .../list_top_solana_mindshare_tokens.ts | 54 +++++++++++++++++++ 8 files changed, 130 insertions(+) create mode 100644 src/langchain/piefun/index.ts create mode 100644 src/langchain/piefun/list_all_baskets.ts create mode 100644 src/langchain/piefun/list_top_solana_mindshare_tokens.ts create mode 100644 src/tools/piefun/index.ts create mode 100644 src/tools/piefun/list_all_baskets.ts create mode 100644 src/tools/piefun/list_top_solana_mindshare_tokens.ts diff --git a/src/agent/index.ts b/src/agent/index.ts index 4e0b5bd..e85bf84 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -175,6 +175,8 @@ import { getTrendingTokensUsingElfaAi, getSmartTwitterAccountStats, } from "../tools/elfa_ai"; +import { listAllBaskets } from "../tools/piefun"; +import { listTopSolanaMindshareTokens } from "../tools/piefun/list_top_solana_mindshare_tokens"; /** * Main class for interacting with Solana blockchain @@ -1241,4 +1243,12 @@ export class SolanaAgentKit { async getTrendingTokensOnCoingecko() { return await getTrendingTokens(this); } + + async listAllBaskets() { + return await listAllBaskets(); + } + + async listTopSolanaMindshareTokens() { + return await listTopSolanaMindshareTokens(); + } } diff --git a/src/langchain/index.ts b/src/langchain/index.ts index 5fcb396..df9938d 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -34,6 +34,7 @@ export * from "./switchboard"; export * from "./elfa_ai"; export * from "./debridge"; export * from "./fluxbeam"; +export * from "./piefun"; import type { SolanaAgentKit } from "../agent"; import { @@ -152,6 +153,8 @@ import { ElfaGetTopMentionsTool, ElfaAccountSmartStatsTool, SolanaFluxbeamCreatePoolTool, + PieFunListAllBaskets, + ListTopSolanaMindshareTokens, } from "./index"; export function createSolanaTools(solanaKit: SolanaAgentKit) { @@ -271,5 +274,7 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) { new ElfaGetTopMentionsTool(solanaKit), new ElfaAccountSmartStatsTool(solanaKit), new SolanaFluxbeamCreatePoolTool(solanaKit), + new PieFunListAllBaskets(solanaKit), + new ListTopSolanaMindshareTokens(solanaKit), ]; } diff --git a/src/langchain/piefun/index.ts b/src/langchain/piefun/index.ts new file mode 100644 index 0000000..d649c37 --- /dev/null +++ b/src/langchain/piefun/index.ts @@ -0,0 +1,2 @@ +export * from "./list_all_baskets"; +export * from "./list_top_solana_mindshare_tokens"; diff --git a/src/langchain/piefun/list_all_baskets.ts b/src/langchain/piefun/list_all_baskets.ts new file mode 100644 index 0000000..c7c5484 --- /dev/null +++ b/src/langchain/piefun/list_all_baskets.ts @@ -0,0 +1,25 @@ +import { Tool } from "langchain/tools"; +import { SolanaAgentKit } from "../../agent"; + +export class PieFunListAllBaskets extends Tool { + name = "piefun_list_all_baskets"; + description = `List all baskets that are available on Pie.Fun (PieFun) + + Inputs: None`; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + async _call(): Promise { + try { + const response = await this.solanaKit.listAllBaskets(); + return JSON.stringify(response); + } catch (error: any) { + return JSON.stringify({ + status: "error", + message: error.message, + }); + } + } +} diff --git a/src/langchain/piefun/list_top_solana_mindshare_tokens.ts b/src/langchain/piefun/list_top_solana_mindshare_tokens.ts new file mode 100644 index 0000000..68fa08f --- /dev/null +++ b/src/langchain/piefun/list_top_solana_mindshare_tokens.ts @@ -0,0 +1,25 @@ +import { Tool } from "langchain/tools"; +import { SolanaAgentKit } from "../../agent"; + +export class ListTopSolanaMindshareTokens extends Tool { + name = "list_top_solana_mindshare_tokens"; + description = `List all the top Solana Mindshare tokens on Cookie.Fun + + Inputs: None`; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + async _call(): Promise { + try { + const response = await this.solanaKit.listTopSolanaMindshareTokens(); + return JSON.stringify(response); + } catch (error: any) { + return JSON.stringify({ + status: "error", + message: error.message, + }); + } + } +} diff --git a/src/tools/piefun/index.ts b/src/tools/piefun/index.ts new file mode 100644 index 0000000..d649c37 --- /dev/null +++ b/src/tools/piefun/index.ts @@ -0,0 +1,2 @@ +export * from "./list_all_baskets"; +export * from "./list_top_solana_mindshare_tokens"; diff --git a/src/tools/piefun/list_all_baskets.ts b/src/tools/piefun/list_all_baskets.ts new file mode 100644 index 0000000..0a3896d --- /dev/null +++ b/src/tools/piefun/list_all_baskets.ts @@ -0,0 +1,7 @@ +export async function listAllBaskets() { + const response = await fetch( + "https://api.internal-pie.fun/v1/basketTokens?filter=state=LISTED", + ); + const data = await response.json(); + return data; +} diff --git a/src/tools/piefun/list_top_solana_mindshare_tokens.ts b/src/tools/piefun/list_top_solana_mindshare_tokens.ts new file mode 100644 index 0000000..b5afbb4 --- /dev/null +++ b/src/tools/piefun/list_top_solana_mindshare_tokens.ts @@ -0,0 +1,54 @@ +import puppeteer from "puppeteer"; + +export async function listTopSolanaMindshareTokens() { + const url = + "https://www.cookie.fun/api/trpc/agents.getAgentsTableDetails?batch=1&input=" + + encodeURIComponent( + JSON.stringify({ + "0": { + json: { + page: 1, + limit: 15, + orderColumn: "TwitterMindshare", + orderByAscending: false, + orderDataPoint: "_7DaysAgo", + tags: [], + projectsFilter: { + blockchainFilter: { chains: [-2] }, + categoriesFilter: { values: [] }, + creationDateFilter: null, + frameworkFilter: { values: [] }, + metricFilters: [], + projectTypeFilter: { values: [] }, + searchFilter: "", + tagsFilter: { values: [] }, + }, + isWatchlist: false, + }, + }, + }), + ); + + const browser = await puppeteer.launch({ headless: true }); // Use headless: false for debugging + const page = await browser.newPage(); + + // Set headers and user agent + await page.setUserAgent( + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36", + ); + await page.setExtraHTTPHeaders({ + Accept: "application/json", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "en-US,en;q=0.9", + Connection: "keep-alive", + }); + + await page.goto(url, { waitUntil: "networkidle2" }); // Wait for requests to settle + + // Extract response from page + const response = await page.evaluate(() => document.body.innerText); + + await browser.close(); + + return response; +}