📇 refactor: Improve State mgmt. for File uploads and Tool Auth (#9359)

* 🔧 fix: Ensure loading state is correctly set when files are empty or in progress

* 🔧 fix: Update ephemeral agent state on file upload error for execute code tool resource

* 🔧 fix: Reset ephemeral agent state for tool when authentication fails

* refactor: Pass conversation prop to FileFormChat and AttachFileChat components
This commit is contained in:
Danny Avila 2025-08-28 23:11:16 -04:00 committed by GitHub
parent 8772b04d1d
commit a26597a696
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 33 additions and 8 deletions

View file

@ -253,7 +253,7 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => {
handleSaveBadges={handleSaveBadges} handleSaveBadges={handleSaveBadges}
setBadges={setBadges} setBadges={setBadges}
/> />
<FileFormChat disableInputs={disableInputs} /> <FileFormChat conversation={conversation} />
{endpoint && ( {endpoint && (
<div className={cn('flex', isRTL ? 'flex-row-reverse' : 'flex-row')}> <div className={cn('flex', isRTL ? 'flex-row-reverse' : 'flex-row')}>
<TextareaAutosize <TextareaAutosize
@ -301,7 +301,7 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => {
)} )}
> >
<div className={`${isRTL ? 'mr-2' : 'ml-2'}`}> <div className={`${isRTL ? 'mr-2' : 'ml-2'}`}>
<AttachFileChat disableInputs={disableInputs} /> <AttachFileChat conversation={conversation} disableInputs={disableInputs} />
</div> </div>
<BadgeRow <BadgeRow
showEphemeralBadges={!isAgentsEndpoint(endpoint) && !isAssistantsEndpoint(endpoint)} showEphemeralBadges={!isAgentsEndpoint(endpoint) && !isAssistantsEndpoint(endpoint)}

View file

@ -7,14 +7,18 @@ import {
isAssistantsEndpoint, isAssistantsEndpoint,
fileConfig as defaultFileConfig, fileConfig as defaultFileConfig,
} from 'librechat-data-provider'; } from 'librechat-data-provider';
import type { EndpointFileConfig } from 'librechat-data-provider'; import type { EndpointFileConfig, TConversation } from 'librechat-data-provider';
import { useGetFileConfig } from '~/data-provider'; import { useGetFileConfig } from '~/data-provider';
import AttachFileMenu from './AttachFileMenu'; import AttachFileMenu from './AttachFileMenu';
import { useChatContext } from '~/Providers';
import AttachFile from './AttachFile'; import AttachFile from './AttachFile';
function AttachFileChat({ disableInputs }: { disableInputs: boolean }) { function AttachFileChat({
const { conversation } = useChatContext(); disableInputs,
conversation,
}: {
disableInputs: boolean;
conversation: TConversation | null;
}) {
const conversationId = conversation?.conversationId ?? Constants.NEW_CONVO; const conversationId = conversation?.conversationId ?? Constants.NEW_CONVO;
const { endpoint, endpointType } = conversation ?? { endpoint: null }; const { endpoint, endpointType } = conversation ?? { endpoint: null };
const isAgents = useMemo(() => isAgentsEndpoint(endpoint), [endpoint]); const isAgents = useMemo(() => isAgentsEndpoint(endpoint), [endpoint]);

View file

@ -1,13 +1,14 @@
import { memo } from 'react'; import { memo } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import type { TConversation } from 'librechat-data-provider';
import { useChatContext } from '~/Providers'; import { useChatContext } from '~/Providers';
import { useFileHandling } from '~/hooks'; import { useFileHandling } from '~/hooks';
import FileRow from './FileRow'; import FileRow from './FileRow';
import store from '~/store'; import store from '~/store';
function FileFormChat({ disableInputs }: { disableInputs: boolean }) { function FileFormChat({ conversation }: { conversation: TConversation | null }) {
const { files, setFiles, setFilesLoading } = useChatContext();
const chatDirection = useRecoilValue(store.chatDirection).toLowerCase(); const chatDirection = useRecoilValue(store.chatDirection).toLowerCase();
const { files, setFiles, conversation, setFilesLoading } = useChatContext();
const { endpoint: _endpoint } = conversation ?? { endpoint: null }; const { endpoint: _endpoint } = conversation ?? { endpoint: null };
const { abortUpload } = useFileHandling(); const { abortUpload } = useFileHandling();

View file

@ -59,10 +59,12 @@ export default function FileRow({
useEffect(() => { useEffect(() => {
if (files.length === 0) { if (files.length === 0) {
setFilesLoading(false);
return; return;
} }
if (files.some((file) => file.progress < 1)) { if (files.some((file) => file.progress < 1)) {
setFilesLoading(true);
return; return;
} }

View file

@ -1,10 +1,13 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { useSetRecoilState } from 'recoil';
import { useToastContext } from '@librechat/client'; import { useToastContext } from '@librechat/client';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { import {
QueryKeys, QueryKeys,
Constants,
EModelEndpoint, EModelEndpoint,
EToolResources,
mergeFileConfig, mergeFileConfig,
isAgentsEndpoint, isAgentsEndpoint,
isAssistantsEndpoint, isAssistantsEndpoint,
@ -19,6 +22,7 @@ import useLocalize, { TranslationKeys } from '~/hooks/useLocalize';
import { useDelayedUploadToast } from './useDelayedUploadToast'; import { useDelayedUploadToast } from './useDelayedUploadToast';
import { processFileForUpload } from '~/utils/heicConverter'; import { processFileForUpload } from '~/utils/heicConverter';
import { useChatContext } from '~/Providers/ChatContext'; import { useChatContext } from '~/Providers/ChatContext';
import { ephemeralAgentByConvoId } from '~/store';
import { logger, validateFiles } from '~/utils'; import { logger, validateFiles } from '~/utils';
import useClientResize from './useClientResize'; import useClientResize from './useClientResize';
import useUpdateFiles from './useUpdateFiles'; import useUpdateFiles from './useUpdateFiles';
@ -39,6 +43,9 @@ const useFileHandling = (params?: UseFileHandling) => {
const abortControllerRef = useRef<AbortController | null>(null); const abortControllerRef = useRef<AbortController | null>(null);
const { startUploadTimer, clearUploadTimer } = useDelayedUploadToast(); const { startUploadTimer, clearUploadTimer } = useDelayedUploadToast();
const { files, setFiles, setFilesLoading, conversation } = useChatContext(); const { files, setFiles, setFilesLoading, conversation } = useChatContext();
const setEphemeralAgent = useSetRecoilState(
ephemeralAgentByConvoId(conversation?.conversationId ?? Constants.NEW_CONVO),
);
const setError = (error: string) => setErrors((prevErrors) => [...prevErrors, error]); const setError = (error: string) => setErrors((prevErrors) => [...prevErrors, error]);
const { addFile, replaceFile, updateFileById, deleteFileById } = useUpdateFiles( const { addFile, replaceFile, updateFileById, deleteFileById } = useUpdateFiles(
params?.fileSetter ?? setFiles, params?.fileSetter ?? setFiles,
@ -133,6 +140,13 @@ const useFileHandling = (params?: UseFileHandling) => {
const error = _error as TError | undefined; const error = _error as TError | undefined;
console.log('upload error', error); console.log('upload error', error);
const file_id = body.get('file_id'); const file_id = body.get('file_id');
const tool_resource = body.get('tool_resource');
if (tool_resource === EToolResources.execute_code) {
setEphemeralAgent((prev) => ({
...prev,
[EToolResources.execute_code]: false,
}));
}
clearUploadTimer(file_id as string); clearUploadTimer(file_id as string);
deleteFileById(file_id as string); deleteFileById(file_id as string);

View file

@ -98,6 +98,10 @@ export function useToolToggle({
if (isAuthenticated !== undefined && !isAuthenticated && setIsDialogOpen) { if (isAuthenticated !== undefined && !isAuthenticated && setIsDialogOpen) {
setIsDialogOpen(true); setIsDialogOpen(true);
e?.preventDefault?.(); e?.preventDefault?.();
setEphemeralAgent((prev) => ({
...(prev || {}),
[toolKey]: false,
}));
return; return;
} }