@plteam/chat-ui
Version:
CUI Kit is a free and open-source library for creating AI assistant chat interfaces, built with React, Material UI, and TypeScript
101 lines (100 loc) • 4.69 kB
JavaScript
import * as React from 'react';
import { useChatContext } from "../../core/ChatGlobalContext";
import { useLocalizationContext } from '../../core/LocalizationContext';
import { langReplace } from '../../../locale/langReplace';
import { ChatViewConstants } from '../../ChatViewConstants';
import AttachmentModel from '../../../models/AttachmentModel';
import { useThreadContext } from '../../thread/ThreadContext';
const useFileAttachment = () => {
const { thread } = useThreadContext();
const { acceptableFileFormat, maxFileSizeBytes, maxFileCount, onFileAttached, snackbar, model } = useChatContext();
const [attachments, setAttachments] = React.useState([]);
const locale = useLocalizationContext();
const acceptableFormatToString = (acceptableFileFormat) => {
if (!acceptableFileFormat)
return '*';
if (Array.isArray(acceptableFileFormat)) {
return acceptableFileFormat.map((f) => ((f.includes('/') || f.includes('.')) ? f : `${f}/*`)).join(',');
}
return acceptableFileFormat;
};
const maxCount = React.useMemo(() => maxFileCount || ChatViewConstants.MAX_ATTACHMENTS_IN_MESSAGE, [maxFileCount]);
const maxSize = React.useMemo(() => maxFileSizeBytes || ChatViewConstants.MAX_ATTACHMENT_SIZE, [maxFileSizeBytes]);
const inputAccept = React.useMemo(() => acceptableFormatToString(acceptableFileFormat), [acceptableFileFormat]);
const checkType = (file, allowedTypes) => {
const fileType = file.type;
const fileExt = '.' + file.name.split('.').pop()?.toLowerCase();
return allowedTypes.some((type) => {
if (type === '*')
return true;
if (type.startsWith('.')) {
return fileExt === type.toLowerCase();
}
if (type.endsWith('/*')) {
const baseType = type.split('/')[0];
return fileType.startsWith(baseType + '/');
}
return fileType === type;
});
};
const handleFileUpload = (fileList) => {
let files = Array.from(fileList || []);
const oversizeFiles = files.filter(f => f.size > maxSize);
if (oversizeFiles.length > 0) {
snackbar.show(langReplace(locale.maxFileSizeWarning, { mb: Math.round(maxSize / 1024 / 1024) }), 'error');
files = files.filter(f => f.size <= maxSize);
}
const allowedTypes = inputAccept.split(',').map(type => type.trim()).filter(type => type);
const invalidFiles = files.filter(f => !checkType(f, allowedTypes));
if (invalidFiles.length > 0) {
snackbar.show(locale.invalidFileTypeWarning, 'error');
files = files.filter(f => !invalidFiles.includes(f));
}
if ((files.length + attachments.length) > maxCount) {
files = files.slice(0, maxCount - attachments.length);
snackbar.show(locale.maxAttachmentWarning, 'error');
}
;
const fileAttachments = files.map((f) => {
const type = f.type.startsWith('video') ? 'video' : f.type.startsWith('image') ? 'image' : 'file';
const data = {
type,
file: f,
};
return new AttachmentModel(data);
});
setAttachments([...attachments, ...fileAttachments]);
if (thread) {
thread.isLoadingAttachments.value = [...thread.isLoadingAttachments.value, ...fileAttachments.map((f) => f.id)];
fileAttachments.forEach((f) => {
const { setProgress, setError, setId, file, id } = f;
const onFinish = () => {
f.progress.value = 100;
thread.isLoadingAttachments.value = thread.isLoadingAttachments.value.filter((a) => a !== id);
};
if (onFileAttached) {
const promise = onFileAttached({ id: f.id, file, actions: { setProgress, setError, onFinish, setId } });
if (promise)
promise.then(() => onFinish());
else {
onFinish();
}
}
else {
onFinish();
}
});
}
model.emitter.emit('onFilesAttached', { ids: fileAttachments.map(v => v.id) });
};
return {
attachments,
setAttachments,
inputAccept,
maxCount,
maxSize,
acceptableFormatToString,
handleFileUpload,
};
};
export default useFileAttachment;