mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-15 23:31:29 +00:00
fix(ui): revoke abandoned upload preview object URLs when the playground unmounts
Image and PDF previews in the playground are object URLs created on file select. Sending or removing the file revokes them, but navigating away with a pending upload dropped the state without revoking, pinning the file blobs until full page unload. Mirror the current preview URLs into a ref and revoke them in an unmount cleanup
This commit is contained in:
parent
2b2ae4ca49
commit
cfb3a6a0e9
2 changed files with 53 additions and 0 deletions
|
|
@ -413,4 +413,47 @@ describe("ChatUI", () => {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("revokes pending upload preview object URLs on unmount", async () => {
|
||||
const createSpy = vi.fn(() => "blob:preview-1");
|
||||
const revokeSpy = vi.fn();
|
||||
const originalCreate = URL.createObjectURL;
|
||||
const originalRevoke = URL.revokeObjectURL;
|
||||
URL.createObjectURL = createSpy;
|
||||
URL.revokeObjectURL = revokeSpy;
|
||||
|
||||
try {
|
||||
const { container, unmount } = render(
|
||||
<ChatUI
|
||||
accessToken="1234567890"
|
||||
token="1234567890"
|
||||
userRole="user"
|
||||
userID="1234567890"
|
||||
disabledPersonalKeyCreation={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('input[type="file"]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement;
|
||||
const file = new File(["image data"], "photo.png", { type: "image/png" });
|
||||
await act(async () => {
|
||||
fireEvent.change(fileInput, { target: { files: [file] } });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createSpy).toHaveBeenCalledWith(file);
|
||||
});
|
||||
expect(revokeSpy).not.toHaveBeenCalledWith("blob:preview-1");
|
||||
|
||||
unmount();
|
||||
|
||||
expect(revokeSpy).toHaveBeenCalledWith("blob:preview-1");
|
||||
} finally {
|
||||
URL.createObjectURL = originalCreate;
|
||||
URL.revokeObjectURL = originalRevoke;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -254,6 +254,16 @@ const ChatUI: React.FC<ChatUIProps> = ({
|
|||
const [chatUploadedImage, setChatUploadedImage] = useState<File | null>(null);
|
||||
const [chatImagePreviewUrl, setChatImagePreviewUrl] = useState<string | null>(null);
|
||||
const [uploadedAudio, setUploadedAudio] = useState<File | null>(null);
|
||||
const pendingPreviewUrlsRef = useRef<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
pendingPreviewUrlsRef.current = [...imagePreviewUrls, responsesImagePreviewUrl, chatImagePreviewUrl].filter(
|
||||
(url): url is string => !!url,
|
||||
);
|
||||
}, [imagePreviewUrls, responsesImagePreviewUrl, chatImagePreviewUrl]);
|
||||
|
||||
useEffect(() => () => pendingPreviewUrlsRef.current.forEach((url) => URL.revokeObjectURL(url)), []);
|
||||
|
||||
const [isGetCodeModalVisible, setIsGetCodeModalVisible] = useState(false);
|
||||
const [generatedCode, setGeneratedCode] = useState("");
|
||||
const [selectedSdk, setSelectedSdk] = useState<"openai" | "azure">("openai");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue