Skip to content

Commit 0cdecb6

Browse files
committed
feat(apps): change to zustand to manage state
1 parent 965b003 commit 0cdecb6

File tree

8 files changed

+550
-185
lines changed

8 files changed

+550
-185
lines changed

apps/playground/app/chat/page.tsx

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Badge } from "@/components/ui/badge"
77
import { ScrollArea } from "@/components/ui/scroll-area"
88
import { Send, Bot } from "lucide-react"
99
import Sidebar from "@/components/sidebar"
10+
import { useAgentStore } from "@/stores/agent-store"
1011

1112

1213
interface ChatMessage {
@@ -27,38 +28,20 @@ interface AgentConfig {
2728
}
2829

2930
export default function ChatPage() {
31+
const { agentKit, isInitialized, config } = useAgentStore()
3032
const [chatInput, setChatInput] = useState("")
3133
const [isLoading, setIsLoading] = useState(false)
32-
const [agentConfig, setAgentConfig] = useState<AgentConfig | null>(null)
3334
const [isClient, setIsClient] = useState(false)
3435

3536
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([])
3637

37-
// Initialize client-side state and load config
38+
// Initialize client-side state
3839
useEffect(() => {
3940
setIsClient(true)
40-
41-
42-
const sync = () => {
43-
const savedConfig = localStorage.getItem("polkadot-agent-config")
44-
if (savedConfig) {
45-
setAgentConfig(JSON.parse(savedConfig))
46-
} else {
47-
setAgentConfig(null)
48-
}
49-
}
50-
sync()
51-
const onStorage = (e: StorageEvent) => {
52-
if (e.key === "polkadot-agent-config") {
53-
sync()
54-
}
55-
}
56-
window.addEventListener("storage", onStorage)
57-
return () => window.removeEventListener("storage", onStorage)
5841
}, [])
5942

6043
const handleSendMessage = async () => {
61-
if (!chatInput.trim() || !agentConfig?.isConfigured) return
44+
if (!chatInput.trim() || !isInitialized || !agentKit) return
6245

6346
const userMessage: ChatMessage = {
6447
id: `user-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
@@ -72,10 +55,46 @@ export default function ChatPage() {
7255
setIsLoading(true)
7356

7457
try {
75-
// Dynamic import to avoid SSR issues
76-
const { AgentService } = await import("@/services/agent")
77-
const agent = AgentService.getInstance()
78-
const result = await agent.ask(userMessage.content)
58+
// Use the shared agentKit from Zustand store to create LangChain agent
59+
const { getLangChainTools } = await import("@polkadot-agent-kit/sdk")
60+
const { AgentExecutor, createToolCallingAgent } = await import("langchain/agents")
61+
const { ChatPromptTemplate } = await import("@langchain/core/prompts")
62+
const { ChatOllama } = await import("@langchain/ollama")
63+
64+
// Get LLM config from localStorage
65+
const llmConfig = localStorage.getItem("llm_config")
66+
if (!llmConfig) throw new Error("LLM configuration not found")
67+
68+
const { provider, model } = JSON.parse(llmConfig)
69+
if (provider !== "ollama") {
70+
throw new Error("Only Ollama is supported in the browser")
71+
}
72+
73+
const llm = new ChatOllama({
74+
model: model || "qwen3:latest",
75+
temperature: 0,
76+
})
77+
78+
const tools = getLangChainTools(agentKit)
79+
const agentPrompt = createToolCallingAgent({
80+
llm: llm as any,
81+
tools: tools as any,
82+
prompt: ChatPromptTemplate.fromMessages([
83+
["system", "You are a helpful Polkadot assistant. Use the available tools to help users with blockchain operations."],
84+
["placeholder", "{chat_history}"],
85+
["human", "{input}"],
86+
["placeholder", "{agent_scratchpad}"],
87+
]) as any,
88+
})
89+
90+
const agentExecutor = new AgentExecutor({
91+
agent: agentPrompt,
92+
tools: tools as any,
93+
verbose: true,
94+
returnIntermediateSteps: true,
95+
})
96+
97+
const result = await agentExecutor.invoke({ input: userMessage.content })
7998
const outputText = typeof result.output === "string" ? result.output : JSON.stringify(result.output, null, 2)
8099
const aiMessage: ChatMessage = {
81100
id: (Date.now() + 1).toString(),
@@ -128,12 +147,12 @@ export default function ChatPage() {
128147
</div>
129148
<div className="flex items-center gap-3">
130149
<Badge
131-
className={`px-3 py-1 ${agentConfig?.isConfigured ? "modern-badge" : "bg-red-900/30 text-red-400 border-red-700"}`}
150+
className={`px-3 py-1 ${isInitialized ? "modern-badge" : "bg-red-900/30 text-red-400 border-red-700"}`}
132151
>
133152
<div
134-
className={`w-2 h-2 rounded-full mr-2 ${agentConfig?.isConfigured ? "bg-green-400 animate-pulse" : "bg-red-400"}`}
153+
className={`w-2 h-2 rounded-full mr-2 ${isInitialized ? "bg-green-400 animate-pulse" : "bg-red-400"}`}
135154
/>
136-
{agentConfig?.isConfigured ? "Agent Ready" : "Configuration Required"}
155+
{isInitialized ? "Agent Ready" : "Configuration Required"}
137156
</Badge>
138157
</div>
139158
</div>
@@ -196,14 +215,14 @@ export default function ChatPage() {
196215
<div className="flex gap-4">
197216
<Textarea
198217
placeholder={
199-
agentConfig?.isConfigured
218+
isInitialized
200219
? "Ask me about Polkadot, XCM, Substrate, or any Web3 topic..."
201220
: "Please configure the agent first..."
202221
}
203222
value={chatInput}
204223
onChange={(e) => setChatInput(e.target.value)}
205224
className="flex-1 min-h-[70px] resize-none modern-input text-base"
206-
disabled={!agentConfig?.isConfigured}
225+
disabled={!isInitialized}
207226
onKeyDown={(e) => {
208227
const ne = (e as any).nativeEvent
209228
if (ne?.isComposing || e.keyCode === 229) return
@@ -217,7 +236,7 @@ export default function ChatPage() {
217236
/>
218237
<Button
219238
onClick={handleSendMessage}
220-
disabled={!chatInput.trim() || isLoading || !agentConfig?.isConfigured}
239+
disabled={!chatInput.trim() || isLoading || !isInitialized}
221240
className="px-8 h-[70px] modern-button-primary text-base font-medium"
222241
>
223242
<Send className="w-5 h-5" />

apps/playground/app/config/page.tsx

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Badge } from "@/components/ui/badge"
1010
import { Settings, Key, Cpu } from "lucide-react"
1111
import Sidebar from "@/components/sidebar"
1212
import { ChainSelector } from "@/components/chain-selector"
13+
import { useAgentStore } from "@/stores/agent-store"
1314
import type { KnownChainId, KeyType } from "@polkadot-agent-kit/common"
1415
interface AgentConfig {
1516
llmProvider: string
@@ -33,6 +34,16 @@ interface Chain {
3334

3435
export default function ConfigPage() {
3536
const router = useRouter()
37+
const {
38+
config,
39+
isConfigured,
40+
isInitialized,
41+
isInitializing,
42+
setConfig,
43+
initializeAgent,
44+
setInitializing
45+
} = useAgentStore()
46+
3647
const [agentConfig, setAgentConfig] = useState<AgentConfig>({
3748
llmProvider: "",
3849
llmModel: "",
@@ -42,7 +53,6 @@ export default function ConfigPage() {
4253
chains: ["paseo", "paseo_people"],
4354
isConfigured: false,
4455
})
45-
const [isConnecting, setIsConnecting] = useState(false)
4656
const [llmConnected, setLlmConnected] = useState<"idle" | "ok" | "error">("idle")
4757
const [availableChains, setAvailableChains] = useState<Chain[]>([])
4858

@@ -111,7 +121,7 @@ export default function ConfigPage() {
111121
return
112122
}
113123

114-
setIsConnecting(true)
124+
setInitializing(true)
115125
setLlmConnected("idle")
116126

117127
try {
@@ -122,14 +132,26 @@ export default function ConfigPage() {
122132
localStorage.removeItem("llm_api_key")
123133
}
124134

125-
// Initialize PolkadotAgentKit for selected chains
126-
const { PolkadotAgentKit } = await import("@polkadot-agent-kit/sdk")
127-
const kit = new PolkadotAgentKit({
135+
// Save LLM config to localStorage
136+
localStorage.setItem("llm_config", JSON.stringify({
137+
provider: agentConfig.llmProvider,
138+
model: agentConfig.llmModel,
139+
apiKey: agentConfig.apiKey
140+
}))
141+
142+
// Create config for Zustand store
143+
const storeConfig = {
128144
privateKey: agentConfig.privateKey,
129-
keyType: agentConfig.keyType as KeyType,
130-
chains: agentConfig.chains as unknown as KnownChainId[],
131-
})
132-
await kit.initializeApi()
145+
keyType: agentConfig.keyType as "Sr25519" | "Ed25519",
146+
chains: agentConfig.chains,
147+
isConfigured: true
148+
}
149+
150+
// Set config in Zustand store
151+
setConfig(storeConfig)
152+
153+
// Initialize agent using Zustand store
154+
await initializeAgent()
133155

134156
// Check LLM connectivity
135157
if (agentConfig.llmProvider === "ollama") {
@@ -151,7 +173,6 @@ export default function ConfigPage() {
151173

152174
const updatedConfig = { ...agentConfig, isConfigured: true }
153175
setAgentConfig(updatedConfig)
154-
localStorage.setItem("polkadot-agent-config", JSON.stringify(updatedConfig))
155176

156177
router.push("/chat")
157178
} catch (err) {
@@ -163,7 +184,7 @@ export default function ConfigPage() {
163184
: "")
164185
)
165186
} finally {
166-
setIsConnecting(false)
187+
setInitializing(false)
167188
}
168189
}
169190

@@ -181,12 +202,12 @@ export default function ConfigPage() {
181202
</div>
182203
<div className="flex items-center gap-3">
183204
<Badge
184-
className={`px-3 py-1 ${agentConfig.isConfigured ? "modern-badge" : "bg-red-900/30 text-red-400 border-red-700"}`}
205+
className={`px-3 py-1 ${isInitialized ? "modern-badge" : "bg-red-900/30 text-red-400 border-red-700"}`}
185206
>
186207
<div
187-
className={`w-2 h-2 rounded-full mr-2 ${agentConfig.isConfigured ? "bg-green-400 animate-pulse" : "bg-red-400"}`}
208+
className={`w-2 h-2 rounded-full mr-2 ${isInitialized ? "bg-green-400 animate-pulse" : "bg-red-400"}`}
188209
/>
189-
{agentConfig.isConfigured ? "Agent Ready" : "Configuration Required"}
210+
{isInitialized ? "Agent Ready" : "Configuration Required"}
190211
</Badge>
191212
</div>
192213
</div>
@@ -315,7 +336,7 @@ export default function ConfigPage() {
315336
}))
316337
}}
317338
availableChains={availableChains}
318-
disabled={isConnecting}
339+
disabled={isInitializing}
319340
/>
320341
</div>
321342
</div>
@@ -324,7 +345,7 @@ export default function ConfigPage() {
324345
<Button
325346
onClick={handleConfigureAgent}
326347
disabled={
327-
isConnecting ||
348+
isInitializing ||
328349
!agentConfig.llmProvider ||
329350
!agentConfig.privateKey ||
330351
agentConfig.chains.length === 0 ||
@@ -333,7 +354,7 @@ export default function ConfigPage() {
333354
className="mt-8 px-8 h-12 text-base font-medium modern-button-primary"
334355
>
335356
<Settings className="w-5 h-5 mr-2" />
336-
{isConnecting ? "Connecting..." : "Connect Agent"}
357+
{isInitializing ? "Connecting..." : "Connect Agent"}
337358
</Button>
338359
</Card>
339360
</div>

0 commit comments

Comments
 (0)