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 { Action, FunctionTool, ActionMetadata, ValidationResult, AssistantsEndpoint, } from 'librechat-data-provider'; import type { ActionAuthForm, ActionWithNullableMetadata } from '~/common'; import type { Spec } from './ActionsTable'; import ActionCallback from '~/components/SidePanel/Builder/ActionCallback'; import { useAssistantsMapContext, useToastContext } from '~/Providers'; import { ActionsTable, columns } from './ActionsTable'; import { useUpdateAction } from '~/data-provider'; 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, endpoint, version, setAction, }: { action?: ActionWithNullableMetadata; assistant_id?: string; endpoint: AssistantsEndpoint; version: number | 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(() => { const rawSpec = action?.metadata?.raw_spec ?? ''; if (!rawSpec) { return; } setInputValue(rawSpec); debouncedValidation(rawSpec, 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 | undefined)?.message ?? localize('com_assistants_update_actions_error'), status: 'error', }); }, }); const saveAction = handleSubmit((authFormData) => { console.log('authFormData', authFormData); const currentAssistantId = assistant_id ?? ''; if (!currentAssistantId) { // alert user? return; } if (!functions) { return; } if (!data) { return; } let { metadata } = action ?? {}; if (!metadata) { metadata = {}; } 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: currentAssistantId, endpoint, version, model: assistantMap?.[endpoint][currentAssistantId].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); }; const submitContext = () => { if (updateAction.isLoading) { return ; } else if (action?.action_id.length ?? 0) { return localize('com_ui_update'); } else { return localize('com_ui_create'); } }; return ( <>
{/* */}