99 EuiButton ,
1010 EuiFieldText ,
1111 EuiFieldNumber ,
12+ EuiSwitch ,
1213 EuiFlexGroup ,
1314 EuiFlexItem ,
1415 EuiFlyout ,
@@ -19,12 +20,14 @@ import {
1920 EuiSpacer ,
2021 EuiTitle ,
2122 EuiCodeBlock ,
23+ EuiLoadingSpinner ,
2224} from '@elastic/eui' ;
2325import { css } from '@emotion/react' ;
2426import React , { useState } from 'react' ;
25- import { useForm , FormProvider , Controller } from 'react-hook-form' ;
26- import type { ToolDefinition } from '@kbn/onechat-common' ;
27+ import { useForm , FormProvider , Controller , type Control } from 'react-hook-form' ;
28+ import type { ToolDefinitionWithSchema } from '@kbn/onechat-common' ;
2729import { i18n } from '@kbn/i18n' ;
30+ import { formatOnechatErrorMessage } from '@kbn/onechat-browser' ;
2831import { useExecuteTool } from '../../../hooks/tools/use_execute_tools' ;
2932import type { ExecuteToolResponse } from '../../../../../common/http_api/tools' ;
3033import { useTool } from '../../../hooks/tools/use_tools' ;
@@ -39,29 +42,132 @@ interface OnechatTestToolFlyout {
3942interface ToolParameter {
4043 name : string ;
4144 label : string ;
45+ description : string ;
4246 value : string ;
4347 type : string ;
4448}
4549
46- const getParameters = ( tool : ToolDefinition | undefined ) : Array < ToolParameter > => {
47- if ( ! tool ) return [ ] ;
50+ enum ToolParameterType {
51+ TEXT = 'text' ,
52+ NUMERIC = 'numeric' ,
53+ BOOLEAN = 'boolean' ,
54+ }
55+
56+ const getComponentType = ( schemaType : string ) : ToolParameterType => {
57+ switch ( schemaType ) {
58+ case 'boolean' :
59+ return ToolParameterType . BOOLEAN ;
60+ case 'integer' :
61+ case 'long' :
62+ case 'number' :
63+ case 'double' :
64+ case 'float' :
65+ return ToolParameterType . NUMERIC ;
66+ default :
67+ return ToolParameterType . TEXT ;
68+ }
69+ } ;
70+
71+ const getParameters = ( tool ?: ToolDefinitionWithSchema ) : Array < ToolParameter > => {
72+ if ( ! tool || ! tool . schema || ! tool . schema . properties ) return [ ] ;
73+
74+ const { properties } = tool . schema ;
4875
4976 const fields : Array < ToolParameter > = [ ] ;
50- if ( tool . configuration && tool . configuration . params ) {
51- const params = tool . configuration . params as Record < string , any > ;
52- Object . entries ( params ) . forEach ( ( [ paramName , paramConfig ] ) => {
53- fields . push ( {
54- name : paramName ,
55- label : paramName ,
56- value : '' ,
57- type : paramConfig . type || 'text' ,
58- } ) ;
77+
78+ Object . entries ( properties ) . forEach ( ( [ paramName , paramSchema ] ) => {
79+ let type = 'string' ; // default fallback
80+
81+ if ( paramSchema && 'type' in paramSchema && paramSchema . type ) {
82+ if ( Array . isArray ( paramSchema . type ) ) {
83+ type = paramSchema . type [ 0 ] ;
84+ } else if ( typeof paramSchema . type === 'string' ) {
85+ type = paramSchema . type ;
86+ }
87+ }
88+
89+ fields . push ( {
90+ name : paramName ,
91+ label : paramSchema ?. title || paramName ,
92+ value : '' ,
93+ description : paramSchema ?. description || '' ,
94+ type,
5995 } ) ;
60- }
96+ } ) ;
6197
6298 return fields ;
6399} ;
64100
101+ const renderFormField = (
102+ field : ToolParameter ,
103+ tool : ToolDefinitionWithSchema ,
104+ control : Control < Record < string , any > >
105+ ) => {
106+ const componentType = getComponentType ( field . type ) ;
107+ const isRequired = tool ?. schema ?. required ?. includes ( field . name ) ;
108+
109+ const commonProps = {
110+ name : field . name ,
111+ control,
112+ rules : {
113+ required : isRequired ? `${ field . label } is required` : false ,
114+ } ,
115+ } ;
116+
117+ switch ( componentType ) {
118+ case ToolParameterType . NUMERIC :
119+ return (
120+ < Controller
121+ { ...commonProps }
122+ render = { ( { field : { onChange, value, name } } ) => (
123+ < EuiFieldNumber
124+ name = { name }
125+ value = { value ?? '' }
126+ type = "number"
127+ onChange = { ( e ) => onChange ( e . target . valueAsNumber || e . target . value ) }
128+ placeholder = { `Enter ${ field . label . toLowerCase ( ) } ` }
129+ fullWidth
130+ />
131+ ) }
132+ />
133+ ) ;
134+
135+ case ToolParameterType . BOOLEAN :
136+ return (
137+ < Controller
138+ { ...commonProps }
139+ defaultValue = { false }
140+ rules = { { required : false } }
141+ render = { ( { field : { onChange, value, name } } ) => (
142+ < EuiSwitch
143+ name = { name }
144+ checked = { Boolean ( value ) }
145+ onChange = { ( e ) => onChange ( e . target . checked ) }
146+ label = { field . label }
147+ />
148+ ) }
149+ />
150+ ) ;
151+
152+ case ToolParameterType . TEXT :
153+ default :
154+ return (
155+ < Controller
156+ { ...commonProps }
157+ render = { ( { field : { onChange, value, name } } ) => (
158+ < EuiFieldText
159+ name = { name }
160+ value = { value ?? '' }
161+ onChange = { ( e ) => onChange ( e . target . value ) }
162+ placeholder = { `Enter ${ field . label . toLowerCase ( ) } ` }
163+ fullWidth
164+ />
165+ ) }
166+ />
167+ ) ;
168+ }
169+ } ;
170+
65171export const OnechatTestFlyout : React . FC < OnechatTestToolFlyout > = ( { toolId, onClose } ) => {
66172 const [ response , setResponse ] = useState < string > ( '{}' ) ;
67173
@@ -74,37 +180,26 @@ export const OnechatTestFlyout: React.FC<OnechatTestToolFlyout> = ({ toolId, onC
74180 formState : { errors } ,
75181 } = form ;
76182
77- const { tool } = useTool ( { toolId } ) ;
183+ const { tool, isLoading } = useTool ( { toolId } ) ;
78184
79185 const { executeTool, isLoading : isExecuting } = useExecuteTool ( {
80186 onSuccess : ( data : ExecuteToolResponse ) => {
81187 setResponse ( JSON . stringify ( data , null , 2 ) ) ;
82188 } ,
83189 onError : ( error : Error ) => {
84- setResponse ( JSON . stringify ( { error : error . message } , null , 2 ) ) ;
190+ setResponse ( JSON . stringify ( { error : formatOnechatErrorMessage ( error ) } , null , 2 ) ) ;
85191 } ,
86192 } ) ;
87193
88194 const onSubmit = async ( formData : Record < string , any > ) => {
89- const toolParams : Record < string , any > = { } ;
90- getParameters ( tool ) . forEach ( ( field ) => {
91- if ( field . name ) {
92- let value = formData [ field . name ] ;
93- if ( field . type === 'integer' || field . type === 'long' ) {
94- value = parseInt ( value , 10 ) ;
95- } else if ( field . type === 'double' || field . type === 'float' ) {
96- value = parseFloat ( value ) ;
97- }
98- toolParams [ field . name ] = value ;
99- }
100- } ) ;
101-
102195 await executeTool ( {
103196 toolId : tool ! . id ,
104- toolParams,
197+ toolParams : formData ,
105198 } ) ;
106199 } ;
107200
201+ if ( ! tool ) return null ;
202+
108203 return (
109204 < EuiFlyout onClose = { onClose } aria-labelledby = "flyoutTitle" >
110205 < EuiFlyoutHeader hasBorder >
@@ -121,99 +216,75 @@ export const OnechatTestFlyout: React.FC<OnechatTestToolFlyout> = ({ toolId, onC
121216 </ EuiFlexGroup >
122217 </ EuiFlyoutHeader >
123218 < EuiFlyoutBody >
124- < FormProvider { ...form } >
125- < EuiFlexGroup gutterSize = "l" responsive = { false } >
126- < EuiFlexItem
127- grow = { false }
128- css = { css `
129- min-width : 200px ;
130- max-width : 300px ;
131- ` }
132- >
133- < EuiTitle size = "s" >
134- < h5 >
135- { i18n . translate ( 'xpack.onechat.tools.testTool.inputsTitle' , {
136- defaultMessage : 'Inputs' ,
137- } ) }
138- </ h5 >
139- </ EuiTitle >
140- < EuiSpacer size = "m" />
141- < EuiForm component = "form" onSubmit = { handleSubmit ( onSubmit ) } >
142- { getParameters ( tool ) ?. map ( ( field ) => (
143- < EuiFormRow
144- key = { field . name }
145- label = { field . label }
146- isInvalid = { ! ! errors [ field . name ] }
147- error = { errors [ field . name ] ?. message as string }
148- >
149- { field . type === 'integer' ||
150- field . type === 'long' ||
151- field . type === 'double' ||
152- field . type === 'float' ? (
153- < Controller
154- name = { field . name }
155- control = { form . control }
156- rules = { { required : `${ field . label } is required` } }
157- render = { ( { field : { onChange, value, name } } ) => (
158- < EuiFieldNumber
159- name = { name }
160- value = { value || '' }
161- onChange = { ( e ) => onChange ( e . target . value ) }
162- placeholder = { `Enter ${ field . label . toLowerCase ( ) } ` }
163- fullWidth
164- />
165- ) }
166- />
167- ) : (
168- < Controller
169- name = { field . name }
170- control = { form . control }
171- rules = { { required : `${ field . label } is required` } }
172- render = { ( { field : { onChange, value, name } } ) => (
173- < EuiFieldText
174- name = { name }
175- value = { value || '' }
176- onChange = { ( e ) => onChange ( e . target . value ) }
177- placeholder = { `Enter ${ field . label . toLowerCase ( ) } ` }
178- fullWidth
179- />
180- ) }
181- />
182- ) }
183- </ EuiFormRow >
184- ) ) }
185- < EuiSpacer size = "m" />
186- < EuiButton type = "submit" size = "s" fill isLoading = { isExecuting } disabled = { ! tool } >
187- { i18n . translate ( 'xpack.onechat.tools.testTool.executeButton' , {
188- defaultMessage : 'Submit' ,
189- } ) }
190- </ EuiButton >
191- </ EuiForm >
219+ { isLoading ? (
220+ < EuiFlexGroup justifyContent = "center" alignItems = "center" >
221+ < EuiFlexItem grow = { false } >
222+ < EuiLoadingSpinner size = "l" />
192223 </ EuiFlexItem >
193- < EuiFlexItem >
194- < EuiTitle size = "s" >
195- < h5 >
196- { i18n . translate ( 'xpack.onechat.tools.testTool.responseTitle' , {
197- defaultMessage : 'Response' ,
198- } ) }
199- </ h5 >
200- </ EuiTitle >
201- < EuiSpacer size = "m" />
202- < EuiCodeBlock
203- language = "json"
204- fontSize = "s"
205- paddingSize = "m"
206- isCopyable = { true }
224+ </ EuiFlexGroup >
225+ ) : (
226+ < FormProvider { ...form } >
227+ < EuiFlexGroup gutterSize = "l" responsive = { false } >
228+ < EuiFlexItem
229+ grow = { false }
207230 css = { css `
208- height : 75 vh ;
209- overflow : auto ;
231+ min-width : 200 px ;
232+ max-width : 300 px ;
210233 ` }
211234 >
212- { response }
213- </ EuiCodeBlock >
214- </ EuiFlexItem >
215- </ EuiFlexGroup >
216- </ FormProvider >
235+ < EuiTitle size = "s" >
236+ < h5 >
237+ { i18n . translate ( 'xpack.onechat.tools.testTool.inputsTitle' , {
238+ defaultMessage : 'Inputs' ,
239+ } ) }
240+ </ h5 >
241+ </ EuiTitle >
242+ < EuiSpacer size = "m" />
243+ < EuiForm component = "form" onSubmit = { handleSubmit ( onSubmit ) } >
244+ { getParameters ( tool ) . map ( ( field ) => (
245+ < EuiFormRow
246+ key = { field . name }
247+ label = { field . label }
248+ helpText = { field . description }
249+ isInvalid = { ! ! errors [ field . name ] }
250+ error = { errors [ field . name ] ?. message as string }
251+ >
252+ { renderFormField ( field , tool , form . control ) }
253+ </ EuiFormRow >
254+ ) ) }
255+ < EuiSpacer size = "m" />
256+ < EuiButton type = "submit" size = "s" fill isLoading = { isExecuting } disabled = { ! tool } >
257+ { i18n . translate ( 'xpack.onechat.tools.testTool.executeButton' , {
258+ defaultMessage : 'Submit' ,
259+ } ) }
260+ </ EuiButton >
261+ </ EuiForm >
262+ </ EuiFlexItem >
263+ < EuiFlexItem >
264+ < EuiTitle size = "s" >
265+ < h5 >
266+ { i18n . translate ( 'xpack.onechat.tools.testTool.responseTitle' , {
267+ defaultMessage : 'Response' ,
268+ } ) }
269+ </ h5 >
270+ </ EuiTitle >
271+ < EuiSpacer size = "m" />
272+ < EuiCodeBlock
273+ language = "json"
274+ fontSize = "s"
275+ paddingSize = "m"
276+ isCopyable = { true }
277+ css = { css `
278+ height : 75vh ;
279+ overflow : auto;
280+ ` }
281+ >
282+ { response }
283+ </ EuiCodeBlock >
284+ </ EuiFlexItem >
285+ </ EuiFlexGroup >
286+ </ FormProvider >
287+ ) }
217288 </ EuiFlyoutBody >
218289 </ EuiFlyout >
219290 ) ;
0 commit comments