Skip to content

Commit 3997157

Browse files
committed
feat: complete Mastra client integration and chat functionality
- Updated context.md to reflect the completion of 9 out of 10 tasks for the Mastra client integration, including installation and implementation of various components. - Marked all relevant tasks as complete in tasks.md, indicating successful installation and configuration of the Mastra Client SDK and related components. - Revised progress.md to include updates on the Mastra Client SDK and Next.js frontend, highlighting the integration of the Mastra client and the chat interface. - Removed outdated test files for model registry, graph RAG query tool, JWT authentication, and vector query tool to clean up the codebase. - Implemented a new chat page (app/chat/page.tsx) that allows users to interact with different agents using the Mastra Client SDK, featuring message streaming and agent selection. - Created a new Mastra client instance in lib/mastra-client.ts to facilitate communication with the Mastra API.
1 parent 7b59aed commit 3997157

13 files changed

Lines changed: 448 additions & 994 deletions

File tree

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,8 @@ SMITHERY_PROFILE='your_smithery_profile_here'
5858
# Other optional, but helpful values
5959
LOG_LEVEL='debug'
6060

61+
# Next.js + Mastra Client SDK
62+
NEXT_PUBLIC_MASTRA_API_URL='http://localhost:4111'
63+
6164
# Example placeholders for local testing
6265
LOCAL_DEV='true'

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,38 @@ cp .env.example .env
161161
### Run Dev Server
162162

163163
```bash
164+
# Terminal 1: Start Mastra backend (agents/tools/workflows at :4111)
164165
npm run dev
166+
167+
# Terminal 2: Start Next.js frontend (at :3000)
168+
npm run dev:next
165169
```
166170

171+
### Next.js + Mastra Client SDK
172+
173+
The frontend uses `@mastra/client-js` to interact with agents:
174+
175+
```typescript
176+
// lib/mastra-client.ts
177+
import { MastraClient } from "@mastra/client-js";
178+
179+
export const mastraClient = new MastraClient({
180+
baseUrl: process.env.NEXT_PUBLIC_MASTRA_API_URL || "http://localhost:4111",
181+
});
182+
183+
// Usage in client components
184+
const agent = mastraClient.getAgent("weatherAgent");
185+
const response = await agent.stream({
186+
messages: [{ role: "user", content: "Hello" }],
187+
});
188+
```
189+
190+
**Pages:**
191+
192+
- `/` - Landing page with agent overview
193+
- `/test` - Server action demo (SSR)
194+
- `/chat` - Client SDK demo (streaming)
195+
167196
### MCP Server (A2A)
168197

169198
```bash

app/chat/page.tsx

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
"use client";
2+
3+
import { useState, useRef, useCallback } from "react";
4+
import { mastraClient } from "@/lib/mastra-client";
5+
import Link from "next/link";
6+
7+
interface Message {
8+
role: "user" | "assistant";
9+
content: string;
10+
}
11+
12+
const AVAILABLE_AGENTS = [
13+
{ id: "weatherAgent", name: "Weather Agent" },
14+
{ id: "researchAgent", name: "Research Agent" },
15+
{ id: "copywriterAgent", name: "Copywriter" },
16+
{ id: "editorAgent", name: "Editor" },
17+
{ id: "reportAgent", name: "Report Agent" },
18+
];
19+
20+
export default function ChatPage() {
21+
const [messages, setMessages] = useState<Message[]>([]);
22+
const [input, setInput] = useState("");
23+
const [isLoading, setIsLoading] = useState(false);
24+
const [selectedAgent, setSelectedAgent] = useState("weatherAgent");
25+
const [streamingContent, setStreamingContent] = useState("");
26+
const abortControllerRef = useRef<AbortController | null>(null);
27+
28+
const handleSubmit = useCallback(
29+
async (e: React.FormEvent) => {
30+
e.preventDefault();
31+
if (!input.trim() || isLoading) return;
32+
33+
const userMessage = input.trim();
34+
setInput("");
35+
setMessages((prev) => [...prev, { role: "user", content: userMessage }]);
36+
setIsLoading(true);
37+
setStreamingContent("");
38+
39+
try {
40+
const agent = mastraClient.getAgent(selectedAgent);
41+
42+
const response = await agent.stream({
43+
messages: [{ role: "user", content: userMessage }],
44+
});
45+
46+
let fullContent = "";
47+
48+
response.processDataStream({
49+
onChunk: async (chunk) => {
50+
if (chunk.type === "text-delta") {
51+
const text = chunk.payload?.text || "";
52+
fullContent += text;
53+
setStreamingContent(fullContent);
54+
}
55+
},
56+
});
57+
58+
await new Promise<void>((resolve) => {
59+
const checkComplete = setInterval(() => {
60+
if (!isLoading) {
61+
clearInterval(checkComplete);
62+
resolve();
63+
}
64+
}, 100);
65+
66+
setTimeout(() => {
67+
clearInterval(checkComplete);
68+
resolve();
69+
}, 30000);
70+
});
71+
72+
setMessages((prev) => [
73+
...prev,
74+
{ role: "assistant", content: fullContent || "No response received." },
75+
]);
76+
} catch (error) {
77+
const errorMessage =
78+
error instanceof Error ? error.message : "An error occurred";
79+
setMessages((prev) => [
80+
...prev,
81+
{ role: "assistant", content: `Error: ${errorMessage}` },
82+
]);
83+
} finally {
84+
setIsLoading(false);
85+
setStreamingContent("");
86+
}
87+
},
88+
[input, isLoading, selectedAgent]
89+
);
90+
91+
const handleStop = useCallback(() => {
92+
abortControllerRef.current?.abort();
93+
setIsLoading(false);
94+
}, []);
95+
96+
return (
97+
<main className="min-h-screen bg-background flex flex-col">
98+
<header className="border-b border-border p-4">
99+
<div className="container mx-auto flex items-center justify-between">
100+
<div className="flex items-center gap-4">
101+
<Link href="/" className="text-xl font-bold text-foreground">
102+
AgentStack
103+
</Link>
104+
<span className="text-muted-foreground">Client SDK Chat</span>
105+
</div>
106+
<select
107+
value={selectedAgent}
108+
onChange={(e) => setSelectedAgent(e.target.value)}
109+
aria-label="Select an agent"
110+
title="Select an agent"
111+
className="bg-card border border-border rounded-md px-3 py-2 text-sm text-foreground"
112+
>
113+
{AVAILABLE_AGENTS.map((agent) => (
114+
<option key={agent.id} value={agent.id}>
115+
{agent.name}
116+
</option>
117+
))}
118+
</select>
119+
</div>
120+
</header>
121+
122+
<div className="flex-1 container mx-auto p-4 overflow-y-auto">
123+
<div className="max-w-3xl mx-auto space-y-4">
124+
{messages.length === 0 && (
125+
<div className="text-center py-12 text-muted-foreground">
126+
<p className="text-lg mb-2">Start a conversation</p>
127+
<p className="text-sm">
128+
Using <code className="bg-muted px-1 rounded">{selectedAgent}</code> via Mastra Client SDK
129+
</p>
130+
</div>
131+
)}
132+
133+
{messages.map((message, index) => (
134+
<div
135+
key={index}
136+
className={`flex ${message.role === "user" ? "justify-end" : "justify-start"}`}
137+
>
138+
<div
139+
className={`max-w-[80%] rounded-lg px-4 py-2 ${
140+
message.role === "user"
141+
? "bg-primary text-primary-foreground"
142+
: "bg-card border border-border text-foreground"
143+
}`}
144+
>
145+
<pre className="whitespace-pre-wrap font-sans text-sm">
146+
{message.content}
147+
</pre>
148+
</div>
149+
</div>
150+
))}
151+
152+
{isLoading && streamingContent && (
153+
<div className="flex justify-start">
154+
<div className="max-w-[80%] rounded-lg px-4 py-2 bg-card border border-border text-foreground">
155+
<pre className="whitespace-pre-wrap font-sans text-sm">
156+
{streamingContent}
157+
<span className="animate-pulse"></span>
158+
</pre>
159+
</div>
160+
</div>
161+
)}
162+
163+
{isLoading && !streamingContent && (
164+
<div className="flex justify-start">
165+
<div className="bg-card border border-border rounded-lg px-4 py-2">
166+
<div className="flex items-center gap-2 text-muted-foreground">
167+
<div className="w-2 h-2 bg-primary rounded-full animate-pulse" />
168+
<span className="text-sm">Thinking...</span>
169+
</div>
170+
</div>
171+
</div>
172+
)}
173+
</div>
174+
</div>
175+
176+
<footer className="border-t border-border p-4">
177+
<form
178+
onSubmit={handleSubmit}
179+
className="container mx-auto max-w-3xl flex gap-2"
180+
>
181+
<input
182+
type="text"
183+
value={input}
184+
onChange={(e) => setInput(e.target.value)}
185+
placeholder={`Ask ${selectedAgent}...`}
186+
disabled={isLoading}
187+
className="flex-1 bg-card border border-border rounded-md px-4 py-2 text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"
188+
/>
189+
{isLoading ? (
190+
<button
191+
type="button"
192+
onClick={handleStop}
193+
className="bg-destructive text-white px-4 py-2 rounded-md font-medium hover:bg-destructive/90 transition-colors"
194+
>
195+
Stop
196+
</button>
197+
) : (
198+
<button
199+
type="submit"
200+
disabled={!input.trim()}
201+
className="bg-primary text-primary-foreground px-4 py-2 rounded-md font-medium hover:bg-primary/90 transition-colors disabled:opacity-50"
202+
>
203+
Send
204+
</button>
205+
)}
206+
</form>
207+
</footer>
208+
</main>
209+
);
210+
}

app/layout.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { Metadata } from "next";
2+
import { Inter } from "next/font/google";
3+
import { ThemeProvider } from "next-themes";
4+
import "./globals.css";
5+
6+
const inter = Inter({ subsets: ["latin"] });
7+
8+
export const metadata: Metadata = {
9+
title: "AgentStack | Multi-Agent Framework",
10+
description:
11+
"Production-grade multi-agent framework for AI applications with 22+ agents, 30+ tools, 10 workflows, and 4 networks.",
12+
applicationName: "AgentStack",
13+
authors: [{ name: "AgentStack Team", url: "https://agentstack.ai" }],
14+
keywords: [
15+
"AI",
16+
"Agents",
17+
"Multi-Agent Systems",
18+
"RAG",
19+
"Observability",
20+
"Governance",
21+
"AgentStack",
22+
],
23+
referrer: "origin-when-cross-origin",
24+
viewport: {
25+
width: "device-width",
26+
initialScale: 1,
27+
},
28+
manifest: "/site.webmanifest",
29+
openGraph: {
30+
title: "AgentStack | Multi-Agent Framework",
31+
description:
32+
"Production-grade multi-agent framework for AI applications with 22+ agents, 30+ tools, 10 workflows, and 4 networks.",
33+
url: "https://agentstack.ai/",
34+
siteName: "AgentStack",
35+
images: ["/logo.svg"],
36+
locale: "en_US",
37+
type: "website",
38+
},
39+
colorScheme: "dark",
40+
};
41+
42+
export default function RootLayout({
43+
children,
44+
}: {
45+
children: React.ReactNode;
46+
}) {
47+
return (
48+
<html lang="en" suppressHydrationWarning>
49+
<body className={inter.className}>
50+
<ThemeProvider
51+
attribute="class"
52+
defaultTheme="system"
53+
enableSystem
54+
disableTransitionOnChange
55+
>
56+
{children}
57+
</ThemeProvider>
58+
</body>
59+
</html>
60+
);
61+
}

0 commit comments

Comments
 (0)