📎 fix: Re-enable Send After File Upload (#14727)

* fix: enable composer send after file upload

* style: sort composer imports
This commit is contained in:
Danny Avila 2026-08-10 15:05:22 -04:00 committed by GitHub
parent 09cbd54f48
commit 9bb599435f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 58 additions and 6 deletions

View file

@ -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}

View file

@ -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());

View file

@ -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) {

View file

@ -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', () => {

View file

@ -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',