import debounce from 'lodash/debounce'; import { useState, useEffect } from 'react'; import { useFormContext } from 'react-hook-form'; import { validateAndParseOpenAPISpec, openapiToFunction, AuthTypeEnum, } from 'librechat-data-provider'; import type { ValidationResult, Action, FunctionTool, ActionMetadata, } from 'librechat-data-provider'; import type { ActionAuthForm } from '~/common'; import type { Spec } from './ActionsTable'; import { useAssistantsMapContext, useToastContext } from '~/Providers'; import { ActionsTable, columns } from './ActionsTable'; import { useUpdateAction } from '~/data-provider'; import { cn, removeFocusOutlines } from '~/utils'; import useLocalize from '~/hooks/useLocalize'; import { Spinner } from '~/components/svg'; const debouncedValidation = debounce( (input: string, callback: (result: ValidationResult) => void) => { const result = validateAndParseOpenAPISpec(input); callback(result); }, 800, ); export default function ActionsInput({ action, assistant_id, setAction, }: { action?: Action; assistant_id?: string; setAction: React.Dispatch>; }) { const handleResult = (result: ValidationResult) => { if (!result.status) { setData(null); setFunctions(null); } setValidationResult(result); }; const localize = useLocalize(); const { showToast } = useToastContext(); const assistantMap = useAssistantsMapContext(); const { handleSubmit, reset } = useFormContext(); const [validationResult, setValidationResult] = useState(null); const [inputValue, setInputValue] = useState(''); const [data, setData] = useState(null); const [functions, setFunctions] = useState(null); useEffect(() => { if (!action?.metadata?.raw_spec) { return; } setInputValue(action.metadata.raw_spec); debouncedValidation(action.metadata.raw_spec, handleResult); }, [action?.metadata?.raw_spec]); useEffect(() => { if (!validationResult || !validationResult.status || !validationResult.spec) { return; } const { functionSignatures, requestBuilders } = openapiToFunction(validationResult.spec); const specs = Object.entries(requestBuilders).map(([name, props]) => { return { name, method: props.method, path: props.path, domain: props.domain, }; }); setData(specs); setValidationResult(null); setFunctions(functionSignatures.map((f) => f.toObjectTool())); }, [validationResult]); const updateAction = useUpdateAction({ onSuccess(data) { showToast({ message: localize('com_assistants_update_actions_success'), status: 'success', }); reset(); setAction(data[2]); }, onError(error) { showToast({ message: (error as Error)?.message ?? localize('com_assistants_update_actions_error'), status: 'error', }); }, }); const saveAction = handleSubmit((authFormData) => { console.log('authFormData', authFormData); if (!assistant_id) { // alert user? return; } if (!functions) { return; } if (!data) { return; } let { metadata = {} } = action ?? {}; const action_id = action?.action_id; metadata.raw_spec = inputValue; const parsedUrl = new URL(data[0].domain); const domain = parsedUrl.hostname; if (!domain) { // alert user? return; } metadata.domain = domain; const { type, saved_auth_fields } = authFormData; const removeSensitiveFields = (obj: ActionMetadata) => { delete obj.auth; delete obj.api_key; delete obj.oauth_client_id; delete obj.oauth_client_secret; }; if (saved_auth_fields && type === AuthTypeEnum.ServiceHttp) { metadata = { ...metadata, api_key: authFormData.api_key, auth: { type, authorization_type: authFormData.authorization_type, custom_auth_header: authFormData.custom_auth_header, }, }; } else if (saved_auth_fields && type === AuthTypeEnum.OAuth) { metadata = { ...metadata, auth: { type, authorization_url: authFormData.authorization_url, client_url: authFormData.client_url, scope: authFormData.scope, token_exchange_method: authFormData.token_exchange_method, }, oauth_client_id: authFormData.oauth_client_id, oauth_client_secret: authFormData.oauth_client_secret, }; } else if (saved_auth_fields) { removeSensitiveFields(metadata); metadata.auth = { type, }; } else { removeSensitiveFields(metadata); } updateAction.mutate({ action_id, metadata, functions, assistant_id, model: assistantMap[assistant_id].model, }); }); const handleInputChange: React.ChangeEventHandler = (event) => { const newValue = event.target.value; setInputValue(newValue); if (!newValue) { setData(null); setFunctions(null); return setValidationResult(null); } debouncedValidation(newValue, handleResult); }; return ( <>
{/* */}