fix: isolate attachment submission state
This commit is contained in:
@@ -301,7 +301,8 @@ export function CodingChatPanel({
|
||||
]);
|
||||
|
||||
const handleAddFiles = useCallback((files: File[]) => {
|
||||
if (!draftKey) return;
|
||||
if (!draftKey
|
||||
|| (targetConversationId && submissionFlightsRef.current.has(targetConversationId))) return;
|
||||
const accepted: LocalComposerAttachment[] = [];
|
||||
const remaining = Math.max(0, CODING_ATTACHMENT_MAX_COUNT - localAttachments.length);
|
||||
let validationError = files.length > remaining
|
||||
@@ -335,10 +336,11 @@ export function CodingChatPanel({
|
||||
...current,
|
||||
[draftKey]: [...(current[draftKey] ?? []), ...accepted],
|
||||
}));
|
||||
}, [draftKey, localAttachments.length]);
|
||||
}, [draftKey, localAttachments.length, targetConversationId]);
|
||||
|
||||
const handleRemoveAttachment = useCallback((id: string) => {
|
||||
if (!draftKey) return;
|
||||
if (!draftKey
|
||||
|| (targetConversationId && submissionFlightsRef.current.has(targetConversationId))) return;
|
||||
setAttachmentsByDraftKey((current) => {
|
||||
const attachment = current[draftKey]?.find((item) => item.id === id);
|
||||
if (attachment) URL.revokeObjectURL(attachment.previewUrl);
|
||||
@@ -349,7 +351,12 @@ export function CodingChatPanel({
|
||||
});
|
||||
uploadedAttachmentsRef.current.delete(id);
|
||||
uploadFlightsRef.current.delete(id);
|
||||
}, [draftKey]);
|
||||
setSubmissionErrors((current) => {
|
||||
const next = { ...current };
|
||||
delete next[draftKey];
|
||||
return next;
|
||||
});
|
||||
}, [draftKey, targetConversationId]);
|
||||
|
||||
const prepareAttachments = useCallback(async (
|
||||
attachments: LocalComposerAttachment[],
|
||||
|
||||
@@ -91,6 +91,7 @@ export function CodingComposer({
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/webp,image/gif"
|
||||
multiple
|
||||
disabled={!editable || submitting}
|
||||
className="sr-only"
|
||||
data-testid="coding-file-attachment-input"
|
||||
onChange={(event) => {
|
||||
@@ -110,8 +111,9 @@ export function CodingComposer({
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute right-1 top-1 flex h-10 w-10 translate-x-2 -translate-y-2 items-end justify-start rounded-full bg-foreground/85 p-1.5 text-background transition-[background-color,scale] duration-150 ease-out hover:bg-foreground active:scale-[0.96]"
|
||||
className="absolute right-1 top-1 flex h-10 w-10 translate-x-2 -translate-y-2 items-end justify-start rounded-full bg-foreground/85 p-1.5 text-background transition-[background-color,scale] duration-150 ease-out hover:bg-foreground active:scale-[0.96] disabled:cursor-not-allowed disabled:opacity-60"
|
||||
aria-label={`移除图片 ${attachment.name}`}
|
||||
disabled={submitting}
|
||||
onClick={() => onRemoveAttachment(attachment.id)}
|
||||
>
|
||||
<X className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
@@ -133,6 +135,7 @@ export function CodingComposer({
|
||||
if (canSend) onSubmit();
|
||||
}}
|
||||
onPaste={(event) => {
|
||||
if (submitting) return;
|
||||
const files = Array.from(event.clipboardData.files).filter((file) => file.type.startsWith('image/'));
|
||||
if (files.length > 0) onAddFiles(files);
|
||||
}}
|
||||
|
||||
@@ -702,10 +702,12 @@ export function createCodingConversationStore(
|
||||
};
|
||||
});
|
||||
|
||||
let attachmentsPrepared = !input.prepareAttachments;
|
||||
try {
|
||||
if (input.prepareAttachments) {
|
||||
submittedDraft.attachments = (await input.prepareAttachments())
|
||||
.map((attachment) => ({ ...attachment }));
|
||||
attachmentsPrepared = true;
|
||||
set((state) => {
|
||||
const currentEntry = state.entriesByConversationId[input.conversationId] ?? emptyEntry();
|
||||
const requests = state.requestsByConversationId[input.conversationId] ?? {};
|
||||
@@ -765,6 +767,7 @@ export function createCodingConversationStore(
|
||||
} catch (error) {
|
||||
const failure = errorDetails(error);
|
||||
const uncertain = failure.code === 'CODING_REQUEST_UNCERTAIN';
|
||||
const preSubmitFailure = !attachmentsPrepared;
|
||||
set((state) => {
|
||||
const currentEntry = state.entriesByConversationId[input.conversationId] ?? emptyEntry();
|
||||
const requests = state.requestsByConversationId[input.conversationId] ?? {};
|
||||
@@ -776,7 +779,11 @@ export function createCodingConversationStore(
|
||||
const reducer = uncertain
|
||||
? currentEntry.reducer
|
||||
: withRejectedNode(currentEntry.reducer, nodeId);
|
||||
const nextEntry = { ...currentEntry, reducer, error: failure.message };
|
||||
const nextEntry = {
|
||||
...currentEntry,
|
||||
reducer,
|
||||
error: preSubmitFailure ? currentEntry.error : failure.message,
|
||||
};
|
||||
return {
|
||||
entriesByConversationId: {
|
||||
...state.entriesByConversationId,
|
||||
|
||||
Reference in New Issue
Block a user