mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 17:30:16 +01:00
🛠️ refactor: Handle .webp, Improve File Life Cycle 📁 (#1213)
* fix: handle webp images correctly * refactor: use the userPath from the start of the filecycle to avoid handling the blob, whose loading may fail upon user request * refactor: delete temp files on reload and new chat
This commit is contained in:
parent
650759306d
commit
cc39074e0a
15 changed files with 160 additions and 66 deletions
|
|
@ -2,6 +2,7 @@ import debounce from 'lodash/debounce';
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import type { BatchFile } from 'librechat-data-provider';
|
||||
import { useDeleteFilesMutation } from '~/data-provider';
|
||||
import { useSetFilesToDelete } from '~/hooks';
|
||||
import { ExtendedFile } from '~/common';
|
||||
import Image from './Image';
|
||||
|
||||
|
|
@ -14,6 +15,7 @@ export default function Images({
|
|||
setFiles: React.Dispatch<React.SetStateAction<Map<string, ExtendedFile>>>;
|
||||
setFilesLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}) {
|
||||
const setFilesToDelete = useSetFilesToDelete();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [_batch, setFileDeleteBatch] = useState<BatchFile[]>([]);
|
||||
const files = Array.from(_files.values());
|
||||
|
|
@ -37,7 +39,7 @@ export default function Images({
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [files]);
|
||||
|
||||
const deleteFiles = useDeleteFilesMutation({
|
||||
const { mutateAsync } = useDeleteFilesMutation({
|
||||
onSuccess: () => {
|
||||
console.log('Files deleted');
|
||||
},
|
||||
|
|
@ -49,10 +51,10 @@ export default function Images({
|
|||
const executeBatchDelete = useCallback(
|
||||
(filesToDelete: BatchFile[]) => {
|
||||
console.log('Deleting files:', filesToDelete);
|
||||
deleteFiles.mutate({ files: filesToDelete });
|
||||
mutateAsync({ files: filesToDelete });
|
||||
setFileDeleteBatch([]);
|
||||
},
|
||||
[deleteFiles],
|
||||
[mutateAsync],
|
||||
);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
@ -81,6 +83,8 @@ export default function Images({
|
|||
const updatedFiles = new Map(currentFiles);
|
||||
updatedFiles.delete(file_id);
|
||||
updatedFiles.delete(temp_file_id);
|
||||
const files = Object.fromEntries(updatedFiles);
|
||||
setFilesToDelete(files);
|
||||
return updatedFiles;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ import { useState } from 'react';
|
|||
import { Settings } from 'lucide-react';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { FC } from 'react';
|
||||
import { useLocalize, useUserKey, useNewConvo, useOriginNavigate } from '~/hooks';
|
||||
import { useLocalize, useUserKey, useOriginNavigate } from '~/hooks';
|
||||
import { SetKeyDialog } from '~/components/Input/SetKeyDialog';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { icons } from './Icons';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
|
|
@ -27,8 +28,8 @@ const MenuItem: FC<MenuItemProps> = ({
|
|||
}) => {
|
||||
const Icon = icons[endpoint] ?? icons.unknown;
|
||||
const [isDialogOpen, setDialogOpen] = useState(false);
|
||||
const { newConversation } = useChatContext();
|
||||
const { getExpiry } = useUserKey(endpoint);
|
||||
const { newConversation } = useNewConvo();
|
||||
const navigate = useOriginNavigate();
|
||||
const localize = useLocalize();
|
||||
const expiryTime = getExpiry();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect, useRef, memo } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { LazyLoadImage } from 'react-lazy-load-image-component';
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import DialogImage from './DialogImage';
|
||||
|
|
@ -19,7 +19,6 @@ const Image = ({
|
|||
// n: number;
|
||||
// i: number;
|
||||
}) => {
|
||||
const prevImagePathRef = useRef<string | null>(null);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const handleImageLoad = () => setIsLoaded(true);
|
||||
const [minDisplayTimeElapsed, setMinDisplayTimeElapsed] = useState(false);
|
||||
|
|
@ -31,17 +30,14 @@ const Image = ({
|
|||
}
|
||||
return () => clearTimeout(timer);
|
||||
}, [isLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
const prevImagePath = prevImagePathRef.current;
|
||||
if (prevImagePath && prevImagePath?.startsWith('blob:') && prevImagePath !== imagePath) {
|
||||
URL.revokeObjectURL(prevImagePath);
|
||||
}
|
||||
prevImagePathRef.current = imagePath;
|
||||
}, [imagePath]);
|
||||
// const makeSquare = n >= 3 && i < 2;
|
||||
|
||||
const placeholderHeight = height > width ? '900px' : '288px';
|
||||
let placeholderHeight = '288px';
|
||||
if (height > width) {
|
||||
placeholderHeight = '900px';
|
||||
} else if (height === width) {
|
||||
placeholderHeight = width + 'px';
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog.Root>
|
||||
|
|
@ -82,4 +78,4 @@ const Image = ({
|
|||
);
|
||||
};
|
||||
|
||||
export default memo(Image);
|
||||
export default Image;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,38 @@
|
|||
import { useEffect } from 'react';
|
||||
import type { ExtendedFile } from '~/common';
|
||||
import { useDragHelpers, useSetFilesToDelete } from '~/hooks';
|
||||
import DragDropOverlay from './Input/Files/DragDropOverlay';
|
||||
import { useDragHelpers } from '~/hooks';
|
||||
import { useDeleteFilesMutation } from '~/data-provider';
|
||||
|
||||
export default function Presentation({ children }: { children: React.ReactNode }) {
|
||||
const { isOver, canDrop, drop } = useDragHelpers();
|
||||
const setFilesToDelete = useSetFilesToDelete();
|
||||
const { mutateAsync } = useDeleteFilesMutation({
|
||||
onSuccess: () => {
|
||||
console.log('Temporary Files deleted');
|
||||
setFilesToDelete({});
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log('Error deleting temporary files:', error);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const filesToDelete = localStorage.getItem('filesToDelete');
|
||||
const map = JSON.parse(filesToDelete ?? '{}') as Record<string, ExtendedFile>;
|
||||
const files = Object.values(map)
|
||||
.filter((file) => file.filepath)
|
||||
.map((file) => ({
|
||||
file_id: file.file_id,
|
||||
filepath: file.filepath as string,
|
||||
}));
|
||||
|
||||
if (files.length === 0) {
|
||||
return;
|
||||
}
|
||||
mutateAsync({ files });
|
||||
}, [mutateAsync]);
|
||||
|
||||
const isActive = canDrop && isOver;
|
||||
return (
|
||||
<div ref={drop} className="relative flex w-full grow overflow-hidden bg-white dark:bg-gray-800">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue