@@ -10,6 +10,12 @@ import { Textarea } from "@/components/ui/textarea"
1010import { Button } from "@/components/ui/button"
1111import { ScrollArea } from "@/components/ui/scroll-area"
1212import { Play , Zap , Terminal } from "lucide-react"
13+ import {
14+ Collapsible ,
15+ CollapsibleContent ,
16+ CollapsibleTrigger ,
17+ } from "@/components/ui/collapsible"
18+ import { ChevronDown } from "lucide-react"
1319
1420type ToolLike = { name ?: string ; description ?: string ; schema ?: any ; schemaJson ?: any ; call : ( args : any ) => Promise < any > }
1521type EndpointKey = "assets" | "swap" | "bifrost" | "staking"
@@ -39,74 +45,46 @@ export default function DeveloperPage() {
3945 const [ selectedMethod , setSelectedMethod ] = useState ( "" )
4046 const [ toolParams , setToolParams ] = useState ( "{}" )
4147 const [ toolCalls , setToolCalls ] = useState < ToolCall [ ] > ( [ ] )
48+ const [ formData , setFormData ] = useState < Record < string , any > > ( { } )
49+ const [ parsedSchema , setParsedSchema ] = useState < any > ( null )
4250
43- useEffect ( ( ) => {
44- const init = async ( ) => {
45- try {
46- const raw = localStorage . getItem ( "polkadot-agent-config" )
47- if ( ! raw ) return
48- const cfg = JSON . parse ( raw ) as AgentConfigLocal
49-
50- const [ { PolkadotAgentKit } , { default : zodToJsonSchema } ] = await Promise . all ( [
51- import ( "@polkadot-agent-kit/sdk" ) ,
52- import ( "zod-to-json-schema" ) ,
53- ] )
54-
55- const kit = new PolkadotAgentKit ( {
56- privateKey : cfg . privateKey ,
57- keyType : cfg . keyType ,
58- chains : cfg . chains as any ,
59- } )
60- await kit . initializeApi ( )
51+ const selectedTool = useMemo ( ( ) => {
52+ if ( ! selectedEndpoint || ! selectedMethod || ! toolsMap ) return null
53+ return toolsMap [ selectedEndpoint ] [ selectedMethod ]
54+ } , [ selectedEndpoint , selectedMethod , toolsMap ] )
6155
62- const ep : ToolsMap = {
63- assets : {
64- getNativeBalanceTool : kit . getNativeBalanceTool ( ) ,
65- transferNativeTool : kit . transferNativeTool ( ) ,
66- xcmTransferNativeTool : kit . xcmTransferNativeTool ( ) ,
67- } ,
68- swap : {
69- swapTokensTool : kit . swapTokensTool ( ) ,
70- } ,
71- bifrost : {
72- mintVdotTool : kit . mintVdotTool ( ) ,
73- } ,
74- staking : {
75- joinPoolTool : kit . joinPoolTool ( ) ,
76- bondExtraTool : kit . bondExtraTool ( ) ,
77- unbondTool : kit . unbondTool ( ) ,
78- withdrawUnbondedTool : kit . withdrawUnbondedTool ( ) ,
79- claimRewardsTool : kit . claimRewardsTool ( ) ,
80- } ,
81- }
56+ const selectedSchemaJson = useMemo ( ( ) => {
57+ const sj = ( selectedTool as any ) ?. schemaJson
58+ return sj ? JSON . stringify ( sj , null , 2 ) : ""
59+ } , [ selectedTool ] )
8260
83- for ( const group of Object . values ( ep ) ) {
84- for ( const [ name , tool ] of Object . entries ( group ) ) {
85- const anyTool = tool as any
86- if ( anyTool ?. schema ) {
87- try {
88- const fullSchema = zodToJsonSchema ( anyTool . schema , name ) as any
61+ // This useEffect handles parsing the schema and initializing the form data
62+ // when a tool is selected.
63+ useEffect ( ( ) => {
64+ const schemaString = ( selectedTool as any ) ?. schemaJson
65+ if ( schemaString && schemaString !== "// Select a method to view schema" ) {
66+ try {
67+ const schema = JSON . parse ( schemaString )
68+ setParsedSchema ( schema )
8969
90- if ( fullSchema ?. $ref && fullSchema ?. definitions ) {
91- const refName = fullSchema . $ref . replace ( '#/definitions/' , '' )
92- anyTool . schemaJson = fullSchema . definitions [ refName ] || fullSchema
93- } else {
94- anyTool . schemaJson = fullSchema
95- }
96- } catch { }
97- }
70+ // Initialize form data with default values from the new schema
71+ const initialFormData : Record < string , any > = { }
72+ if ( schema . properties ) {
73+ for ( const [ key , prop ] of Object . entries ( schema . properties as Record < string , any > ) ) {
74+ initialFormData [ key ] = prop . default ?? ""
9875 }
9976 }
100-
101- setToolsMap ( ep )
102- setAgentReady ( true )
77+ setFormData ( initialFormData )
10378 } catch ( e ) {
104- console . error ( "Developer init failed:" , e )
105- setAgentReady ( false )
79+ console . error ( "Failed to parse schema:" , e )
80+ setParsedSchema ( null )
81+ setFormData ( { } )
10682 }
83+ } else {
84+ setParsedSchema ( null )
85+ setFormData ( { } )
10786 }
108- init ( )
109- } , [ ] )
87+ } , [ selectedTool ] )
11088
11189 const staticMethods : Record < EndpointKey , string [ ] > = {
11290 assets : [
@@ -135,24 +113,46 @@ export default function DeveloperPage() {
135113 return dynamic . length ? dynamic : staticMethods [ selectedEndpoint ]
136114 } , [ selectedEndpoint , toolsMap ] )
137115
138- const selectedTool = useMemo ( ( ) => {
139- if ( ! selectedEndpoint || ! selectedMethod || ! toolsMap ) return null
140- return toolsMap [ selectedEndpoint ] [ selectedMethod ]
141- } , [ selectedEndpoint , selectedMethod , toolsMap ] )
116+ // This function will render the correct input field based on the schema property type.
117+ const renderParameterInput = ( key : string , prop : any ) => {
118+ const handleInputChange = ( value : any ) => {
119+ setFormData ( prev => ( { ...prev , [ key ] : value } ) )
120+ }
142121
143- const selectedSchemaJson = useMemo ( ( ) => {
144- const sj = ( selectedTool as any ) ?. schemaJson
145- return sj ? JSON . stringify ( sj , null , 2 ) : ""
146- } , [ selectedTool ] )
122+ // If the property is an enum, render a dropdown select.
123+ if ( prop . enum ) {
124+ return (
125+ < Select onValueChange = { handleInputChange } value = { formData [ key ] } >
126+ < SelectTrigger className = "modern-select" >
127+ < SelectValue placeholder = { `Select ${ prop . description || key } ...` } />
128+ </ SelectTrigger >
129+ < SelectContent >
130+ { prop . enum . map ( ( option : string ) => (
131+ < SelectItem key = { option } value = { option } > { option } </ SelectItem >
132+ ) ) }
133+ </ SelectContent >
134+ </ Select >
135+ )
136+ }
137+
138+ // Render a standard text input for other types.
139+ return (
140+ < Input
141+ placeholder = { prop . description || key }
142+ value = { formData [ key ] || "" }
143+ onChange = { e => handleInputChange ( e . target . value ) }
144+ className = "modern-input"
145+ />
146+ )
147+ }
147148
148- const runTool = async ( ) => {
149+ const runTool = async ( params : Record < string , any > ) => {
149150 if ( ! selectedTool ) return
150151 const id = String ( Date . now ( ) )
151- setToolCalls ( prev => [ ...prev , { id, tool : selectedEndpoint || "" , method : selectedMethod , params : toolParams , status : "pending" } ] )
152+ setToolCalls ( prev => [ ...prev , { id, tool : selectedEndpoint || "" , method : selectedMethod , params : JSON . stringify ( params , null , 2 ) , status : "pending" } ] )
152153 try {
153- const parsed = toolParams ? JSON . parse ( toolParams ) : { }
154- console . log ( "[Developer] Executing:" , { endpoint : selectedEndpoint , method : selectedMethod , params : parsed } )
155- const res = await ( selectedTool as any ) . call ( parsed )
154+ console . log ( "[Developer] Executing:" , { endpoint : selectedEndpoint , method : selectedMethod , params : params } )
155+ const res = await ( selectedTool as any ) . call ( params )
156156 console . log ( "[Developer] Response:" , res )
157157 setToolCalls ( prev => prev . map ( c => c . id === id ? { ...c , status : "success" , response : JSON . stringify ( res , null , 2 ) } : c ) )
158158 } catch ( err : any ) {
@@ -189,10 +189,11 @@ export default function DeveloperPage() {
189189 Polkadot API Tools
190190 </ h3 >
191191
192- < div className = "grid grid-cols-1 lg:grid-cols-3 gap-3 sm:gap-4" >
193- < div className = "col-span-1" >
194- < label className = "text-sm font-semibold mb-3 block modern-text-primary" > Select API Endpoint</ label >
195- < Select value = { selectedEndpoint } onValueChange = { ( v ) => { setSelectedEndpoint ( v as EndpointKey ) ; setSelectedMethod ( "" ) ; } } >
192+ < div className = "space-y-4" >
193+ { /* Step 1: Select API Endpoint */ }
194+ < div className = "space-y-2" >
195+ < label className = "text-sm font-semibold block modern-text-primary" > Select API Endpoint</ label >
196+ < Select value = { selectedEndpoint } onValueChange = { ( v ) => { setSelectedEndpoint ( v as EndpointKey ) ; setSelectedMethod ( "" ) ; setParsedSchema ( null ) ; setFormData ( { } ) ; } } >
196197 < SelectTrigger className = "h-12 modern-select" >
197198 < SelectValue placeholder = "Choose endpoint..." />
198199 </ SelectTrigger >
@@ -202,61 +203,59 @@ export default function DeveloperPage() {
202203 < SelectItem value = "bifrost" > Bifrost</ SelectItem >
203204 < SelectItem value = "staking" > Staking</ SelectItem >
204205 </ SelectContent >
205- </ Select >
206- </ div >
207-
208- < div className = "col-span-1" >
209- < label className = "text-sm font-semibold mb-3 block modern-text-primary" > Method</ label >
210- < Select value = { selectedMethod } onValueChange = { setSelectedMethod } disabled = { ! selectedEndpoint } >
211- < SelectTrigger className = "h-12 modern-select" >
212- < SelectValue placeholder = "Choose method..." />
213- </ SelectTrigger >
214- < SelectContent >
215- { methodOptions . map ( m => (
216- < SelectItem key = { m } value = { m } > { m } </ SelectItem >
217- ) ) }
218- </ SelectContent >
219206 </ Select >
220207 </ div >
221208
222- < div className = "col-span-1" >
223- < label className = "text-sm font-semibold mb-3 block modern-text-primary" > Quick Params</ label >
224- < Input
225- className = "h-12 modern-input"
226- placeholder = 'e.g. {"chain":"paseo"}'
227- onChange = { ( e ) => setToolParams ( e . target . value ) }
228- value = { toolParams }
229- />
230- </ div >
231- </ div >
209+ { /* Step 2: Select Method (appears after endpoint is selected) */ }
210+ { selectedEndpoint && (
211+ < div className = "space-y-2" >
212+ < label className = "text-sm font-semibold block modern-text-primary" > Method</ label >
213+ < Select value = { selectedMethod } onValueChange = { setSelectedMethod } disabled = { ! selectedEndpoint } >
214+ < SelectTrigger className = "h-12 modern-select" >
215+ < SelectValue placeholder = "Choose method..." />
216+ </ SelectTrigger >
217+ < SelectContent >
218+ { methodOptions . map ( m => (
219+ < SelectItem key = { m } value = { m } > { m } </ SelectItem >
220+ ) ) }
221+ </ SelectContent >
222+ </ Select >
223+ </ div >
224+ ) }
232225
233- < div className = "mt-3 sm:mt-4 grid grid-cols-1 lg:grid-cols-2 gap-3 sm:gap-4" >
234- < div >
235- < label className = "text-sm font-semibold mb-2 sm:mb-3 block modern-text-primary" > Parameters (JSON)</ label >
236- < Textarea
237- className = "font-mono text-xs sm:text-sm modern-input min-h-[100px] sm:min-h-[120px]"
238- value = { toolParams }
239- onChange = { ( e ) => setToolParams ( e . target . value ) }
240- placeholder = "{}"
241- rows = { 4 }
242- />
243- </ div >
244- < div >
245- < label className = "text-sm font-semibold mb-2 sm:mb-3 block modern-text-primary" > Schema (readonly)</ label >
246- < pre className = "text-xs bg-black/30 p-2 sm:p-3 rounded-lg font-mono overflow-x-auto border border-white/10 min-h-[100px] sm:min-h-[120px] text-white leading-relaxed" >
247- { selectedSchemaJson || "// Select a method to view schema" }
248- </ pre >
226+ { /* Step 3: Parameters (appears after method is selected and schema is parsed) */ }
227+ { selectedMethod && parsedSchema ?. properties && (
228+ < div className = "space-y-4 pt-4 border-t border-white/10" >
229+ { Object . entries ( parsedSchema . properties ) . map ( ( [ key , prop ] : [ string , any ] ) => (
230+ < Collapsible key = { key } defaultOpen >
231+ < CollapsibleTrigger className = "flex justify-between items-center w-full text-left" >
232+ < h4 className = "text-md font-semibold flex items-center gap-2" >
233+ { key }
234+ < span className = "text-xs font-mono text-gray-400" > ({ prop . type } )</ span >
235+ </ h4 >
236+ < ChevronDown className = "h-4 w-4 transition-transform [&[data-state=open]]:rotate-180" />
237+ </ CollapsibleTrigger >
238+ < CollapsibleContent className = "pt-2" >
239+ < p className = "text-xs text-gray-400 mb-2" > { prop . description } </ p >
240+ { renderParameterInput ( key , prop ) }
241+ </ CollapsibleContent >
242+ </ Collapsible >
243+ ) ) }
244+ </ div >
245+ ) }
246+
247+ { /* Execute Button */ }
248+ < div className = "pt-4 border-t border-white/10" >
249+ < Button
250+ onClick = { ( ) => runTool ( formData ) }
251+ disabled = { ! agentReady || ! selectedEndpoint || ! selectedMethod || ! parsedSchema }
252+ className = "w-full modern-button-primary"
253+ >
254+ < Play className = "w-4 h-4 sm:w-5 sm:h-5 mr-2" />
255+ Execute API Call
256+ </ Button >
249257 </ div >
250258 </ div >
251-
252- < Button
253- onClick = { runTool }
254- disabled = { ! agentReady || ! selectedEndpoint || ! selectedMethod }
255- className = "mt-4 sm:mt-6 px-6 sm:px-8 h-10 sm:h-12 text-sm sm:text-base font-medium modern-button-primary w-full sm:w-auto"
256- >
257- < Play className = "w-4 h-4 sm:w-5 sm:h-5 mr-2" />
258- Execute API Call
259- </ Button >
260259 </ Card >
261260
262261 < Card className = "p-3 sm:p-4 lg:p-6 modern-card" >
0 commit comments