| import React, { useMemo, useCallback, useEffect } from 'react'; | |
| import debounce from 'lodash/debounce'; | |
| import { TerminalSquareIcon } from 'lucide-react'; | |
| import { Tools, AuthType } from 'librechat-data-provider'; | |
| import { Spinner, useToastContext } from '@librechat/client'; | |
| import type { CodeBarProps } from '~/common'; | |
| import { useVerifyAgentToolAuth, useToolCallMutation } from '~/data-provider'; | |
| import ApiKeyDialog from '~/components/SidePanel/Agents/Code/ApiKeyDialog'; | |
| import { useLocalize, useCodeApiKeyForm } from '~/hooks'; | |
| import { useMessageContext } from '~/Providers'; | |
| import { cn, normalizeLanguage } from '~/utils'; | |
| const RunCode: React.FC<CodeBarProps> = React.memo(({ lang, codeRef, blockIndex }) => { | |
| const localize = useLocalize(); | |
| const { showToast } = useToastContext(); | |
| const execute = useToolCallMutation(Tools.execute_code, { | |
| onError: () => { | |
| showToast({ message: localize('com_ui_run_code_error'), status: 'error' }); | |
| }, | |
| }); | |
| const { messageId, conversationId, partIndex } = useMessageContext(); | |
| const normalizedLang = useMemo(() => normalizeLanguage(lang), [lang]); | |
| const { data } = useVerifyAgentToolAuth( | |
| { toolId: Tools.execute_code }, | |
| { | |
| retry: 1, | |
| }, | |
| ); | |
| const authType = useMemo(() => data?.message ?? false, [data?.message]); | |
| const isAuthenticated = useMemo(() => data?.authenticated ?? false, [data?.authenticated]); | |
| const { methods, onSubmit, isDialogOpen, setIsDialogOpen, handleRevokeApiKey } = | |
| useCodeApiKeyForm({}); | |
| const handleExecute = useCallback(async () => { | |
| if (!isAuthenticated) { | |
| setIsDialogOpen(true); | |
| return; | |
| } | |
| const codeString: string = codeRef.current?.textContent ?? ''; | |
| if ( | |
| typeof codeString !== 'string' || | |
| codeString.length === 0 || | |
| typeof normalizedLang !== 'string' || | |
| normalizedLang.length === 0 | |
| ) { | |
| return; | |
| } | |
| execute.mutate({ | |
| partIndex, | |
| messageId, | |
| blockIndex, | |
| conversationId: conversationId ?? '', | |
| lang: normalizedLang, | |
| code: codeString, | |
| }); | |
| }, [ | |
| codeRef, | |
| execute, | |
| partIndex, | |
| messageId, | |
| blockIndex, | |
| conversationId, | |
| normalizedLang, | |
| setIsDialogOpen, | |
| isAuthenticated, | |
| ]); | |
| const debouncedExecute = useMemo( | |
| () => debounce(handleExecute, 1000, { leading: true }), | |
| [handleExecute], | |
| ); | |
| useEffect(() => { | |
| return () => { | |
| debouncedExecute.cancel(); | |
| }; | |
| }, [debouncedExecute]); | |
| if (typeof normalizedLang !== 'string' || normalizedLang.length === 0) { | |
| return null; | |
| } | |
| return ( | |
| <> | |
| <button | |
| type="button" | |
| className={cn('ml-auto flex gap-2')} | |
| onClick={debouncedExecute} | |
| disabled={execute.isLoading} | |
| > | |
| {execute.isLoading ? ( | |
| <Spinner className="animate-spin" size={18} /> | |
| ) : ( | |
| <TerminalSquareIcon size={18} /> | |
| )} | |
| {localize('com_ui_run_code')} | |
| </button> | |
| <ApiKeyDialog | |
| onSubmit={onSubmit} | |
| isOpen={isDialogOpen} | |
| register={methods.register} | |
| onRevoke={handleRevokeApiKey} | |
| onOpenChange={setIsDialogOpen} | |
| handleSubmit={methods.handleSubmit} | |
| isToolAuthenticated={isAuthenticated} | |
| isUserProvided={authType === AuthType.USER_PROVIDED} | |
| /> | |
| </> | |
| ); | |
| }); | |
| export default RunCode; | |