mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-28 12:44:28 +00:00
📎 fix: Re-enable Send After File Upload (#14727)
* fix: enable composer send after file upload * style: sort composer imports
This commit is contained in:
parent
09cbd54f48
commit
9bb599435f
5 changed files with 58 additions and 6 deletions
|
|
@ -22,10 +22,10 @@ import {
|
|||
useAddedChatContext,
|
||||
useAssistantsMapContext,
|
||||
} from '~/Providers';
|
||||
import { cn, getModelSpec, hasIncompleteFiles, removeFocusRings } from '~/utils';
|
||||
import PendingManualSkillsChips from './PendingManualSkillsChips';
|
||||
import useAskAnswerMode from '~/hooks/Input/useAskAnswerMode';
|
||||
import AskUserQuestionPopover from './AskUserQuestionPopover';
|
||||
import { cn, getModelSpec, removeFocusRings } from '~/utils';
|
||||
import InterruptSteerButton from './InterruptSteerButton';
|
||||
import DuringRunSendButton from './DuringRunSendButton';
|
||||
import { useGetStartupConfig } from '~/data-provider';
|
||||
|
|
@ -59,7 +59,6 @@ interface ChatFormProps {
|
|||
setFiles: FileSetter;
|
||||
conversation: TConversation | null;
|
||||
isSubmitting: boolean;
|
||||
filesLoading: boolean;
|
||||
setFilesLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
newConversation: ConvoGenerator;
|
||||
handleStopGenerating: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
|
|
@ -73,7 +72,6 @@ const ChatForm = memo(function ChatForm({
|
|||
setFiles,
|
||||
conversation,
|
||||
isSubmitting,
|
||||
filesLoading,
|
||||
setFilesLoading,
|
||||
newConversation,
|
||||
handleStopGenerating,
|
||||
|
|
@ -123,6 +121,7 @@ const ChatForm = memo(function ChatForm({
|
|||
[conversation?.spec, startupConfig],
|
||||
);
|
||||
const hideBadgeRow = modelSpec?.hideBadgeRow === true;
|
||||
const filesLoading = useMemo(() => hasIncompleteFiles(files), [files]);
|
||||
const conversationId = useMemo(
|
||||
() => conversation?.conversationId ?? Constants.NEW_CONVO,
|
||||
[conversation?.conversationId],
|
||||
|
|
@ -726,7 +725,6 @@ function ChatFormWrapper({ index = 0, placeholder }: { index?: number; placehold
|
|||
setFiles,
|
||||
conversation,
|
||||
isSubmitting,
|
||||
filesLoading,
|
||||
setFilesLoading,
|
||||
newConversation,
|
||||
handleStopGenerating,
|
||||
|
|
@ -785,7 +783,6 @@ function ChatFormWrapper({ index = 0, placeholder }: { index?: number; placehold
|
|||
setFiles={setFiles}
|
||||
conversation={stableConversation}
|
||||
isSubmitting={isSubmitting}
|
||||
filesLoading={filesLoading}
|
||||
setFilesLoading={setFilesLoading}
|
||||
newConversation={stableNewConversation}
|
||||
handleStopGenerating={stableHandleStop}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,24 @@ describe('useFileHandling', () => {
|
|||
const loadHook = async () => (await import('../useFileHandling')).default;
|
||||
|
||||
describe('endpointOverride', () => {
|
||||
it('clears the loading state when file validation throws', async () => {
|
||||
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||
mockValidateFiles.mockImplementationOnce(() => {
|
||||
throw new Error('invalid file config');
|
||||
});
|
||||
|
||||
const useFileHandling = await loadHook();
|
||||
const { result } = renderHook(() => useFileHandling());
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleFiles([new File(['hello'], 'test.txt', { type: 'text/plain' })]);
|
||||
});
|
||||
|
||||
expect(mockSetFilesLoading).toHaveBeenCalledWith(false);
|
||||
expect(mockMutate).not.toHaveBeenCalled();
|
||||
consoleError.mockRestore();
|
||||
});
|
||||
|
||||
it('uploads non-HEIC images without running HEIC conversion', async () => {
|
||||
const useFileHandling = await loadHook();
|
||||
const { result } = renderHook(() => useFileHandling());
|
||||
|
|
|
|||
|
|
@ -306,6 +306,7 @@ const useFileHandlingCore = (params: UseFileHandling | undefined, fileState: Fil
|
|||
} catch (error) {
|
||||
console.error('file validation error', error);
|
||||
setError('com_error_files_validation');
|
||||
setFilesLoading(false);
|
||||
return;
|
||||
}
|
||||
if (!filesAreValid) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,31 @@
|
|||
import { normalizeExportFilename } from './files';
|
||||
import type { ExtendedFile } from '~/common';
|
||||
import { hasIncompleteFiles, normalizeExportFilename } from './files';
|
||||
|
||||
describe('hasIncompleteFiles', () => {
|
||||
const createFile = (file_id: string, progress: number): ExtendedFile => ({
|
||||
file_id,
|
||||
progress,
|
||||
size: 1,
|
||||
});
|
||||
|
||||
it('returns true while any attachment is still uploading', () => {
|
||||
const files = new Map([
|
||||
['complete', createFile('complete', 1)],
|
||||
['uploading', createFile('uploading', 0.9)],
|
||||
]);
|
||||
|
||||
expect(hasIncompleteFiles(files)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false as soon as every attachment is complete', () => {
|
||||
const files = new Map([
|
||||
['first', createFile('first', 1)],
|
||||
['second', createFile('second', 1)],
|
||||
]);
|
||||
|
||||
expect(hasIncompleteFiles(files)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeExportFilename', () => {
|
||||
it('replaces every whitespace run with a single underscore', () => {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,15 @@ import type { ExtendedFile } from '~/common';
|
|||
|
||||
export const partialTypes = ['text/x-'];
|
||||
|
||||
export function hasIncompleteFiles(files: Map<string, ExtendedFile>): boolean {
|
||||
for (const file of files.values()) {
|
||||
if (file.progress < 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const textDocument = {
|
||||
paths: TextPaths,
|
||||
fill: '#FF5588',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue