-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathpage.tsx
More file actions
209 lines (193 loc) · 8.83 KB
/
page.tsx
File metadata and controls
209 lines (193 loc) · 8.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
"use client"
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Textarea } from "@/components/ui/textarea"
import { Badge } from "@/components/ui/badge"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Send, Bot } from "lucide-react"
import Sidebar from "@/components/sidebar"
interface ChatMessage {
id: string
type: "user" | "ai"
content: string
timestamp: Date
}
interface AgentConfig {
llmProvider: string
llmModel: string
apiKey: string
privateKey: string
keyType: string
chains: string[]
isConfigured: boolean
}
export default function ChatPage() {
const [chatInput, setChatInput] = useState("")
const [isLoading, setIsLoading] = useState(false)
const [agentConfig, setAgentConfig] = useState<AgentConfig | null>(null)
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([
{
id: "welcome",
type: "ai",
content:
'Welcome to the Polkadot Agent Playground!\n\nI\'m your AI assistant for exploring the Polkadot ecosystem. Ready to dive into the world of interoperable blockchains?\n\n**What I can help you with:**\n• Polkadot governance and democracy\n• Cross-chain interoperability with XCM\n• Substrate development guidance\n• Parachain ecosystem insights\n\n**Try asking:**\n- "How does XCM work for cross-chain messaging?"\n- "What are the benefits of Substrate framework?"\n- "Explain Polkadot\'s shared security model"\n- "How do I build a parachain?"',
timestamp: new Date(),
},
])
// Load config from localStorage on mount
useEffect(() => {
const sync = () => {
const savedConfig = localStorage.getItem("polkadot-agent-config")
if (savedConfig) {
setAgentConfig(JSON.parse(savedConfig))
} else {
setAgentConfig(null)
}
}
sync()
const onStorage = (e: StorageEvent) => {
if (e.key === "polkadot-agent-config") {
sync()
}
}
window.addEventListener("storage", onStorage)
return () => window.removeEventListener("storage", onStorage)
}, [])
const handleSendMessage = async () => {
if (!chatInput.trim() || !agentConfig?.isConfigured) return
const userMessage: ChatMessage = {
id: Date.now().toString(),
type: "user",
content: chatInput,
timestamp: new Date(),
}
setChatMessages((prev) => [...prev, userMessage])
setChatInput("")
setIsLoading(true)
// Simulate AI response
setTimeout(() => {
const responses = [
"Great question about Polkadot! XCM (Cross-Consensus Messaging) enables seamless communication between parachains, allowing them to share functionality and assets while maintaining their specialized purposes.",
"Substrate is a powerful blockchain framework that provides modular components for building custom blockchains. It handles the complex networking, consensus, and runtime logic so you can focus on your chain's unique features.",
"Polkadot's shared security model means all parachains benefit from the same level of security as the relay chain, without needing to bootstrap their own validator sets. This is a game-changer for blockchain interoperability.",
"Building a parachain involves using Substrate to create your runtime logic, then bidding for a parachain slot through the auction system. The process has become much more accessible with tools like Zombienet for testing.",
]
const aiMessage: ChatMessage = {
id: (Date.now() + 1).toString(),
type: "ai",
content: responses[Math.floor(Math.random() * responses.length)],
timestamp: new Date(),
}
setChatMessages((prev) => [...prev, aiMessage])
setIsLoading(false)
}, 1500)
}
return (
<div className="modern-container">
<div className="flex h-screen">
<Sidebar currentPage="chat" />
<div className="flex-1 flex flex-col">
<div className="border-b border-white/10 modern-card border-l-0 border-r-0 border-t-0 rounded-none">
<div className="flex items-center justify-between p-6">
<div className="flex items-center gap-4">
<h2 className="text-2xl font-bold modern-text-primary">AI Chat Interface</h2>
<Badge className="modern-badge font-medium px-3 py-1">Interactive</Badge>
</div>
<div className="flex items-center gap-3">
<Badge
className={`px-3 py-1 ${agentConfig?.isConfigured ? "modern-badge" : "bg-red-900/30 text-red-400 border-red-700"}`}
>
<div
className={`w-2 h-2 rounded-full mr-2 ${agentConfig?.isConfigured ? "bg-green-400 animate-pulse" : "bg-red-400"}`}
/>
{agentConfig?.isConfigured ? "Agent Ready" : "Configuration Required"}
</Badge>
</div>
</div>
</div>
<div className="flex-1 flex flex-col">
<ScrollArea className="flex-1 p-6">
<div className="space-y-6 max-w-4xl mx-auto">
{chatMessages.map((message) => (
<div key={message.id} className={`flex ${message.type === "user" ? "justify-end" : "justify-start"}`}>
<div
className={`max-w-[85%] rounded-2xl p-5 ${
message.type === "user" ? "modern-button-primary ml-12" : "modern-card mr-12"
}`}
>
<div className="flex items-start gap-4">
{message.type === "ai" && (
<div className="w-8 h-8 rounded-lg modern-logo flex items-center justify-center flex-shrink-0 mt-1">
<Bot className="w-4 h-4" />
</div>
)}
<div className="flex-1">
<div className="whitespace-pre-wrap text-sm leading-relaxed">{message.content}</div>
<div className="text-xs opacity-60 mt-3 font-mono">
{message.timestamp.toLocaleTimeString()}
</div>
</div>
</div>
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="modern-card rounded-2xl p-5 mr-12">
<div className="flex items-center gap-4">
<div className="w-8 h-8 rounded-lg modern-logo flex items-center justify-center animate-pulse">
<Bot className="w-4 h-4" />
</div>
<div className="flex items-center gap-1">
<div className="w-2 h-2 bg-white rounded-full animate-bounce" />
<div
className="w-2 h-2 bg-white rounded-full animate-bounce"
style={{ animationDelay: "0.1s" }}
/>
<div
className="w-2 h-2 bg-white rounded-full animate-bounce"
style={{ animationDelay: "0.2s" }}
/>
</div>
</div>
</div>
</div>
)}
</div>
</ScrollArea>
<div className="border-t border-white/10 modern-card border-l-0 border-r-0 border-b-0 rounded-none p-6">
<div className="max-w-4xl mx-auto">
<div className="flex gap-4">
<Textarea
placeholder={
agentConfig?.isConfigured
? "Ask me about Polkadot, XCM, Substrate, or any Web3 topic..."
: "Please configure the agent first..."
}
value={chatInput}
onChange={(e) => setChatInput(e.target.value)}
className="flex-1 min-h-[70px] resize-none modern-input text-base"
disabled={!agentConfig?.isConfigured}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault()
handleSendMessage()
}
}}
/>
<Button
onClick={handleSendMessage}
disabled={!chatInput.trim() || isLoading || !agentConfig?.isConfigured}
className="px-8 h-[70px] modern-button-primary text-base font-medium"
>
<Send className="w-5 h-5" />
</Button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}