Skip to content

Commit

Permalink
Merge pull request #1 from plutohan/init-client
Browse files Browse the repository at this point in the history
Initialize chatbot client
  • Loading branch information
plutohan authored Mar 4, 2025
2 parents a085738 + 3c71f63 commit 1ab6a47
Show file tree
Hide file tree
Showing 32 changed files with 5,948 additions and 1,059 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
35 changes: 34 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,37 @@ node_modules

.DS_Store
.turbo
.turbo/
.turbo/

# dependencies
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
1 change: 1 addition & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
80 changes: 80 additions & 0 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { NextRequest, NextResponse } from "next/server";
import { ChatOpenAI } from "@langchain/openai";
import { MemorySaver } from "@langchain/langgraph";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { SolanaAgentKit, createSolanaTools } from "../../../src/index";

const llm = new ChatOpenAI({
temperature: 0.7,
model: "gpt-4o",
});

const solanaAgent = new SolanaAgentKit(
process.env.SOLANA_PRIVATE_KEY!,
process.env.RPC_URL!,
{
OPENAI_API_KEY: process.env.OPENAI_API_KEY!,
COINGECKO_DEMO_API_KEY: process.env.COINGECKO_DEMO_API_KEY!,
},
);

const tools = createSolanaTools(solanaAgent);
const memory = new MemorySaver();

// Cast the agent to the new type
const agent = createReactAgent({
llm,
tools,
checkpointSaver: memory,
messageModifier: `
You are a helpful agent that can interact onchain using the Solana Agent Kit. You are
empowered to interact onchain using your tools. If you ever need funds, you can request them from the
faucet. If not, you can provide your wallet details and request funds from the user. If there is a 5XX
(internal) HTTP error code, ask the user to try again later. If someone asks you to do something you
can't do with your currently available tools, you must say so, and encourage them to implement it
themselves using the Solana Agent Kit, recommend they go to https://www.solanaagentkit.xyz for more information. Be
concise and helpful with your responses. Refrain from restating your tools' descriptions unless it is explicitly requested.
`,
});

export async function POST(req: NextRequest) {
try {
const body = await req.json();
const messages = body.messages ?? [];

console.log(messages);

const eventStream = agent.streamEvents(
{
messages,
},
{
version: "v2",
configurable: {
thread_id: "Solana Agent Kit!",
},
},
);
console.log("eventStream", eventStream);
const textEncoder = new TextEncoder();
const transformStream = new ReadableStream({
async start(controller) {
for await (const { event, data } of eventStream) {
if (event === "on_chat_model_stream") {
if (data.chunk.content) {
controller.enqueue(textEncoder.encode(data.chunk.content));
}
}
}
controller.close();
},
});

console.log("transformStream", transformStream);

return new Response(transformStream);
} catch (e: any) {
console.log("error", e);
return NextResponse.json({ error: e.message }, { status: e.status ?? 500 });
}
}
33 changes: 33 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
color: #f8f8f8;
background: #131318;
}

body input,
body textarea {
color: black;
}

a {
color: #2d7bd4;
}

a:hover {
border-bottom: 1px solid;
}

p {
margin: 8px 0;
}

code {
color: #ffa500;
}

li {
padding: 4px;
}
45 changes: 45 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import "./globals.css";
import { Public_Sans } from "next/font/google";

const publicSans = Public_Sans({ subsets: ["latin"] });

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<head>
<title>SolanaAgentKit + LangChain + Next.js Template</title>
<link rel="shortcut icon" href="/images/favicon.ico" />
<meta
name="description"
content="Starter template showing how to use SolanaAgentKit with Langchain in Next.js projects."
/>
<meta
property="og:title"
content="SolanaAgentKit + LangChain + Next.js Template"
/>
<meta
property="og:description"
content="Starter template showing how to use SolanaAgentKit with LangChain in Next.js projects."
/>
<meta property="og:image" content="/images/title-card.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="SolanaAgentKit + LangChain + Next.js Template"
/>
<meta
name="twitter:description"
content="Starter template showing how to use SolanaAgentKit with LangChain in Next.js projects."
/>
<meta name="twitter:image" content="/images/title-card.png" />
</head>
<body className={publicSans.className}>
<div className="flex flex-col p-4 md:p-12 h-[100vh]">{children}</div>
</body>
</html>
);
}
75 changes: 75 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { ChatWindow } from "@/components/ChatWindow";

export default function Home() {
const InfoCard = (
<div className="p-4 md:p-8 rounded bg-[#25252d] w-full max-h-[85%] overflow-hidden">
<h1 className="text-3xl md:text-4xl mb-4">
SolanaAgentKit + LangChain.js 🦜🔗 + Next.js
</h1>
<ul>
<li className="text-l">
🤝
<span className="ml-2">
This template showcases a simple agent chatbot using{" "}
<a href="https://www.solanaagentkit.xyz/">SolanaAgentKit</a>
{", "}
<a href="https://js.langchain.com/" target="_blank">
LangChain.js
</a>{" "}
and the Vercel{" "}
<a href="https://sdk.vercel.ai/docs" target="_blank">
AI SDK
</a>{" "}
in a{" "}
<a href="https://nextjs.org/" target="_blank">
Next.js
</a>{" "}
project.
</span>
</li>
<li className="hidden text-l md:block">
💻
<span className="ml-2">
You can find the prompt and model logic for this use-case in{" "}
<code>app/api/chat/route.ts</code>.
</span>
</li>
<li className="hidden text-l md:block">
🎨
<span className="ml-2">
The main frontend logic is found in <code>app/page.tsx</code>.
</span>
</li>
<li className="text-l">
🐙
<span className="ml-2">
This template is open source - you can see the source code and
deploy your own version{" "}
<a
href="https://github.com/michaelessiet/solana-agent-nextjs-starter-langchain"
target="_blank"
>
from the GitHub repo
</a>
!
</span>
</li>
<li className="text-l">
👇
<span className="ml-2">
Try asking e.g. <code>What is my wallet address?</code> below!
</span>
</li>
</ul>
</div>
);
return (
<ChatWindow
endpoint="api/chat"
emoji="🤖"
titleText="Solana agent"
placeholder="I'm your friendly Solana agent! Ask me anything..."
emptyStateComponent={InfoCard}
></ChatWindow>
);
}
58 changes: 58 additions & 0 deletions components/ChatMessageBubble.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import markdownToHtml from "@/utils/markdownToHTML";
import type { Message } from "ai/react";
import { useMemo } from "react";

export function ChatMessageBubble(props: {
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 content = useMemo(() => {
return markdownToHtml(props.message.content);
}, [props.message.content]);

return (
<div
className={`${alignmentClassName} ${colorClassName} rounded px-4 py-2 max-w-[80%] mb-8 flex`}
>
<div className="mr-2">{prefix}</div>
<div className="flex flex-col">
<div
className="prose prose-lg max-w-none"
dangerouslySetInnerHTML={{ __html: content }}
></div>
{props.sources && props.sources.length ? (
<>
<code className="mt-4 mr-auto bg-slate-600 px-2 py-1 rounded">
<h2>🔍 Sources:</h2>
</code>
<code className="mt-1 mr-2 bg-slate-600 px-2 py-1 rounded text-xs">
{props.sources?.map((source, i) => (
<div className="mt-2" key={"source:" + i}>
{i + 1}. &quot;{source.pageContent}&quot;
{source.metadata?.loc?.lines !== undefined ? (
<div>
<br />
Lines {source.metadata?.loc?.lines?.from} to{" "}
{source.metadata?.loc?.lines?.to}
</div>
) : (
""
)}
</div>
))}
</code>
</>
) : (
""
)}
</div>
</div>
);
}
Loading

0 comments on commit 1ab6a47

Please sign in to comment.