diff --git a/frontend/src/index.css b/frontend/src/index.css index 3be0ec921..af822e4a4 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -50,6 +50,67 @@ animation: loading-dot 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } +@keyframes show-after-delay { + 0%, 80% { + opacity: 0; + max-height: 0em; + } + 93.3% { + opacity: 0; + max-height: 1.5em; + } + 100% { + opacity: 1; + } +} +.animate-show-after-7s { + animation: show-after-delay 5.5s forwards; +} + +@keyframes alternate-fade-first { + 0% { + opacity: 0; + } + 10% { + opacity: 1; + } + 40% { + opacity: 1; + } + 50% { + opacity: 0; + } + 100% { + opacity: 0; + } +} + +@keyframes alternate-fade-second { + 0% { + opacity: 0; + } + 50% { + opacity: 0; + } + 60% { + opacity: 1; + } + 90% { + opacity: 1; + } + 100% { + opacity: 0; + } +} + +.animate-alternate-first { + animation: alternate-fade-first 10s infinite; +} + +.animate-alternate-second { + animation: alternate-fade-second 10s infinite; +} + .white-shadow { box-shadow: 0 -20px 16px 4px white; } diff --git a/frontend/src/pages/chat/conversation/ChatInput.tsx b/frontend/src/pages/chat/conversation/ChatInput.tsx index 1e8810091..f41dc0679 100644 --- a/frontend/src/pages/chat/conversation/ChatInput.tsx +++ b/frontend/src/pages/chat/conversation/ChatInput.tsx @@ -35,7 +35,6 @@ export function ChatInput({ textareaRef, chatId, configuration, isDisabled, isEm const { updateContext, context } = useExtensionContext(chatId); const [defaultValues, setDefaultValues] = useState({}); const { - uploadingFiles, fullFileSlots, allowedFileNameExtensions, chatFiles, @@ -202,12 +201,41 @@ export function ChatInput({ textareaRef, chatId, configuration, isDisabled, isEm {isEmpty && }
- {chatFiles.map((file) => ( - - ))} - {uploadingFiles.map((file, n) => ( - - ))} + {(() => { + const uploadingFilesWithMetadata = uploadMutations + .filter((m) => m.status === 'pending') + .map((m, index) => ({ + fileName: m.variables?.file?.name || '', + isUploading: true, + originalFile: m.variables?.file, + uploadTime: new Date(), + uniqueKey: `${m.submittedAt}-${index}`, + })); + + const filesWithMetadata = [ + ...uploadingFilesWithMetadata, + ...chatFiles.map((file) => ({ + fileName: file.fileName, + isUploading: false, + originalFile: file, + uploadTime: new Date(file.uploadedAt), + uniqueKey: `chat-${file.id}`, + })), + ].sort((a, b) => { + const alphabeticalOrder = a.fileName.localeCompare(b.fileName); + if (alphabeticalOrder !== 0) return alphabeticalOrder; + return a.uploadTime.getTime() - b.uploadTime.getTime(); + }); + + return filesWithMetadata.map((file) => ( + + )); + })()}
{extensionFilterChips.map( diff --git a/frontend/src/pages/chat/conversation/FileItem.tsx b/frontend/src/pages/chat/conversation/FileItem.tsx index 431ef4580..68370572a 100644 --- a/frontend/src/pages/chat/conversation/FileItem.tsx +++ b/frontend/src/pages/chat/conversation/FileItem.tsx @@ -1,7 +1,10 @@ import { IconFile, IconRotate2, IconTrash } from '@tabler/icons-react'; -import React from 'react'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { FileDto } from 'src/api'; +import { cn } from 'src/lib'; import { extractType } from 'src/pages/utils'; +import { texts } from 'src/texts'; type FileItemProps = { file: FileDto | { fileName: string }; @@ -10,26 +13,35 @@ type FileItemProps = { }; export const FileItemComponent = ({ file, onRemove, loading }: FileItemProps) => { + const [isDeleting, setIsDeleting] = useState(false); + const { i18n } = useTranslation(); const fileName = file.fileName; const fileType = 'mimeType' in file ? extractType(file) : undefined; const handleRemove = (e: React.MouseEvent) => { e.preventDefault(); - if (onRemove && 'id' in file) { + if (onRemove && 'id' in file && !isDeleting) { + setIsDeleting(true); onRemove(file); } }; return (
-
-
- {loading ? : } +
+
+ {loading ? : } {fileType && ( - + {fileType} )} @@ -38,15 +50,36 @@ export const FileItemComponent = ({ file, onRemove, loading }: FileItemProps) =>
{fileName} + {loading && ( +
+ + {texts.files.waitingMessage1} + + + {texts.files.waitingMessage2} + +
+ )} + {!loading && 'uploadedAt' in file && file.uploadedAt && ( +
+ {new Date(file.uploadedAt).toLocaleString(i18n.language === 'de' ? 'de-DE' : undefined)} +
+ )}
{!loading && onRemove && ( -
- -
+ )}
diff --git a/frontend/src/pages/chat/useChatDropzone.ts b/frontend/src/pages/chat/useChatDropzone.ts index 7ded3f6ca..a953d5ba9 100644 --- a/frontend/src/pages/chat/useChatDropzone.ts +++ b/frontend/src/pages/chat/useChatDropzone.ts @@ -31,8 +31,7 @@ export const useChatDropzone = () => { .filter((m) => m.status === 'pending') .map((m) => m.variables?.file) .filter(Boolean) - .map((f) => f!) - .filter((f) => !chatFiles?.some((chatFile) => chatFile?.fileName === f?.name)); + .map((f) => f!); const getFileSlots = () => { return userBucket?.extensions.map((x) => { diff --git a/frontend/src/texts/index.ts b/frontend/src/texts/index.ts index fef364ce9..5d22cc099 100644 --- a/frontend/src/texts/index.ts +++ b/frontend/src/texts/index.ts @@ -314,6 +314,8 @@ function load() { uploadImageFailedUseThePaperclip: (fileName: string) => translate('files.uploadImageFailedUseThePaperclip', { fileName }), uploading: translate('files.uploading'), uploadMultiple: (fileCount: number) => translate('files.uploadMultiple', { fileCount }), + waitingMessage1: translate('files.waitingMessage1'), + waitingMessage2: translate('files.waitingMessage2'), wholeFileTooLarge: translate('files.wholeFileTooLarge'), }, login: { diff --git a/frontend/src/texts/languages/de.ts b/frontend/src/texts/languages/de.ts index 1a5411e0a..25e473f84 100644 --- a/frontend/src/texts/languages/de.ts +++ b/frontend/src/texts/languages/de.ts @@ -293,6 +293,8 @@ export const de: typeof en = { 'Datei {{fileName}} konnte nicht hochgeladen werden. Bilder können über das 📎-Symbol im Nachrichtenfenster hochgeladen werden.', uploading: 'Datei wird hochgeladen', uploadMultiple: '{{fileCount}} Dateien werden hochgeladen...', + waitingMessage1: 'lädt...', + waitingMessage2: 'noch einige Minuten', wholeFileTooLarge: 'Die Datei ist größer als die definierte maximale Dateigröße', }, login: { diff --git a/frontend/src/texts/languages/en.ts b/frontend/src/texts/languages/en.ts index 29badd6ba..315a383db 100644 --- a/frontend/src/texts/languages/en.ts +++ b/frontend/src/texts/languages/en.ts @@ -287,6 +287,8 @@ export const en = { 'Failed to upload file {{fileName}}. Images can be uploaded via the 📎 symbol in the message window.', uploading: 'Uploading File', uploadMultiple: 'Uploading {{fileCount}} files...', + waitingMessage1: 'processing...', + waitingMessage2: 'a few minutes more', wholeFileTooLarge: 'The file is larger than the defined file size limit', }, login: {