Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions apps/playground/app/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,22 @@ export default function ChatPage() {

// Load config from localStorage on mount
useEffect(() => {
const savedConfig = localStorage.getItem("polkadot-agent-config")
if (savedConfig) {
setAgentConfig(JSON.parse(savedConfig))
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 () => {
Expand Down
122 changes: 98 additions & 24 deletions apps/playground/app/config/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { Badge } from "@/components/ui/badge"
import { Settings, Key, Cpu } from "lucide-react"
import Sidebar from "@/components/sidebar"
import type { KnownChainId, KeyType } from "@polkadot-agent-kit/common"

interface AgentConfig {
llmProvider: string
Expand All @@ -31,15 +32,24 @@ export default function ConfigPage() {
chains: ["paseo", "paseo_people"],
isConfigured: false,
})
const [isConnecting, setIsConnecting] = useState(false)
const [llmConnected, setLlmConnected] = useState<"idle" | "ok" | "error">("idle")

const llmProviders = [
{ value: "openai", label: "OpenAI", models: ["gpt-4", "gpt-4-turbo", "gpt-3.5-turbo"] },
{ value: "ollama", label: "Ollama", models: ["llama2", "codellama", "mistral", "neural-chat"] },
{ value: "anthropic", label: "Anthropic", models: ["claude-3-opus", "claude-3-sonnet", "claude-3-haiku"] },
{ value: "openai", label: "OpenAI", models: ["gpt-4o-mini"] },
{ value: "ollama", label: "Ollama", models: ["qwen3:latest"] },
]

const keyTypes = ["Sr25519", "Ed25519", "Ecdsa"]
const availableChains = ["polkadot", "kusama", "paseo", "paseo_people", "westend", "rococo"]
const keyTypes: KeyType[] = ["Sr25519", "Ed25519"]
// Use KnownChainId values only; keep a focused subset commonly used in tests/examples
const availableChains: KnownChainId[] = [
"paseo",
"paseo_people",
"west",
"west_asset_hub",
"polkadot",
"kusama",
]

// Load config from localStorage on mount
useEffect(() => {
Expand All @@ -49,29 +59,83 @@ export default function ConfigPage() {
}
}, [])

const handleConfigureAgent = () => {
if (!agentConfig.llmProvider || !agentConfig.apiKey || !agentConfig.privateKey) {
alert("Please fill in all required fields")
const handleConfigureAgent = async () => {
const needsApiKey = agentConfig.llmProvider === "openai"
if (!agentConfig.llmProvider) {
alert("Please select an LLM provider")
return
}
if (needsApiKey && !agentConfig.apiKey) {
alert("API key is required for OpenAI")
return
}
if (!agentConfig.privateKey) {
alert("Private key is required")
return
}
if (!agentConfig.keyType) {
alert("Please select a key type")
return
}
if (!agentConfig.chains || agentConfig.chains.length === 0) {
alert("Please select at least one chain")
return
}

// Initialize PolkadotAgentKit (simulated)
console.log("[v0] Initializing PolkadotAgentKit with config:", {
privateKey: agentConfig.privateKey.substring(0, 10) + "...",
keyType: agentConfig.keyType,
chains: agentConfig.chains,
llmProvider: agentConfig.llmProvider,
llmModel: agentConfig.llmModel,
})
setIsConnecting(true)
setLlmConnected("idle")

const updatedConfig = { ...agentConfig, isConfigured: true }
setAgentConfig(updatedConfig)
try {
// Save API key temporarily in localStorage (client-side only)
if (needsApiKey) {
localStorage.setItem("llm_api_key", agentConfig.apiKey)
} else {
localStorage.removeItem("llm_api_key")
}

// Save to localStorage
localStorage.setItem("polkadot-agent-config", JSON.stringify(updatedConfig))
// Initialize PolkadotAgentKit for selected chains
const { PolkadotAgentKit } = await import("@polkadot-agent-kit/sdk")
const kit = new PolkadotAgentKit({
privateKey: agentConfig.privateKey,
keyType: agentConfig.keyType as KeyType,
chains: agentConfig.chains as unknown as KnownChainId[],
})
await kit.initializeApi()

// Redirect to chat
router.push("/chat")
// Check LLM connectivity
if (agentConfig.llmProvider === "ollama") {
// Try ping local Ollama
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 2500)
try {
const res = await fetch("http://127.0.0.1:11434/api/tags", { signal: controller.signal })
setLlmConnected(res.ok ? "ok" : "error")
} catch (_) {
setLlmConnected("error")
} finally {
clearTimeout(timeout)
}
} else if (agentConfig.llmProvider === "openai") {
// We avoid calling OpenAI from browser due to CORS/security; assume ok if key provided
setLlmConnected("ok")
}

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

router.push("/chat")
} catch (err) {
console.error("Failed to connect agent:", err)
alert(
`Failed to connect agent: ${err instanceof Error ? err.message : "Unknown error"}. ` +
(agentConfig.llmProvider === "ollama" && llmConnected === "error"
? "Ollama seems unreachable at 127.0.0.1:11434. Ensure Ollama is running and the model is pulled."
: "")
)
} finally {
setIsConnecting(false)
}
}

return (
Expand Down Expand Up @@ -162,7 +226,11 @@ export default function ConfigPage() {
value={agentConfig.apiKey}
onChange={(e) => setAgentConfig((prev) => ({ ...prev, apiKey: e.target.value }))}
className="h-12 modern-input font-mono"
disabled={agentConfig.llmProvider === "ollama"}
/>
{agentConfig.llmProvider === "ollama" && (
<p className="text-xs modern-text-secondary mt-2">API key not required for Ollama.</p>
)}
</div>
</Card>

Expand Down Expand Up @@ -236,11 +304,17 @@ export default function ConfigPage() {

<Button
onClick={handleConfigureAgent}
disabled={!agentConfig.llmProvider || !agentConfig.apiKey || !agentConfig.privateKey}
disabled={
isConnecting ||
!agentConfig.llmProvider ||
!agentConfig.privateKey ||
agentConfig.chains.length === 0 ||
(agentConfig.llmProvider === "openai" && !agentConfig.apiKey)
}
className="mt-8 px-8 h-12 text-base font-medium modern-button-primary"
>
<Settings className="w-5 h-5 mr-2" />
Configure Agent
{isConnecting ? "Connecting..." : "Connect Agent"}
</Button>
</Card>
</div>
Expand Down
20 changes: 16 additions & 4 deletions apps/playground/app/developer/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,24 @@ export default function DeveloperPage() {
{ value: "identity", label: "Identity Registry", description: "On-chain identity management" },
]

// Load config from localStorage on mount
// Load config from localStorage on mount and react to updates
useEffect(() => {
const savedConfig = localStorage.getItem("polkadot-agent-config")
if (savedConfig) {
setAgentConfig(JSON.parse(savedConfig))
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 handleRunTool = async () => {
Expand Down
21 changes: 17 additions & 4 deletions apps/playground/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,24 @@ export default function Sidebar({ currentPage }: SidebarProps) {

// Load config from localStorage on mount
useEffect(() => {
const savedConfig = localStorage.getItem("polkadot-agent-config")
if (savedConfig) {
const config = JSON.parse(savedConfig)
setAgentConfig({ isConfigured: config.isConfigured || false })
const sync = () => {
const savedConfig = localStorage.getItem("polkadot-agent-config")
if (savedConfig) {
const config = JSON.parse(savedConfig)
setAgentConfig({ isConfigured: config.isConfigured || false })
} else {
setAgentConfig({ isConfigured: false })
}
}
sync()
// Listen for changes from other tabs/pages
const onStorage = (e: StorageEvent) => {
if (e.key === "polkadot-agent-config") {
sync()
}
}
window.addEventListener("storage", onStorage)
return () => window.removeEventListener("storage", onStorage)
}, [])

const navigateTo = (page: string) => {
Expand Down
9 changes: 9 additions & 0 deletions apps/playground/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ const nextConfig = {
images: {
unoptimized: true,
},
webpack: (config) => {
// Enable WebAssembly used by downstream SDK deps
config.experiments = {
...(config.experiments || {}),
asyncWebAssembly: true,
layers: true,
}
return config
},
}

export default nextConfig
8 changes: 7 additions & 1 deletion apps/playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
},
"dependencies": {
"@hookform/resolvers": "^3.10.0",
"@langchain/core": "^0.3.40",
"@polkadot-agent-kit/common": "workspace:*",
"@polkadot-agent-kit/core": "workspace:*",
"@polkadot-agent-kit/llm": "workspace:*",
"@polkadot-agent-kit/sdk": "workspace:*",
"@radix-ui/react-accordion": "1.2.2",
"@radix-ui/react-alert-dialog": "1.1.4",
"@radix-ui/react-aspect-ratio": "1.1.1",
Expand Down Expand Up @@ -59,7 +64,8 @@
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.9",
"zod": "3.25.67"
"zod": "3.25.67",
"zod-to-json-schema": "^3.24.3"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.9",
Expand Down
Loading