From 398a31f4265e4cc9f450ff2caccf9ed097478673 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 17 Jan 2024 00:39:14 +0000 Subject: [PATCH] UI touchup --- packages/server/src/Interface.ts | 5 + packages/server/src/index.ts | 87 +++--- packages/ui/src/api/chatflows.js | 1 + .../ui/src/ui-component/button/ImageButton.js | 57 ++++ .../ui-component/cards/StarterPromptsCard.css | 1 - .../ui-component/cards/StarterPromptsCard.js | 7 +- .../ui/src/views/chatmessage/ChatMessage.css | 32 +-- .../ui/src/views/chatmessage/ChatMessage.js | 268 ++++++++++-------- .../src/views/chatmessage/audio-recording.js | 11 + 9 files changed, 297 insertions(+), 172 deletions(-) create mode 100644 packages/ui/src/ui-component/button/ImageButton.js diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 942fe490..a944e064 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -214,3 +214,8 @@ export interface ICredentialReqBody { export interface ICredentialReturnResponse extends ICredential { plainDataObj: ICredentialDataDecrypted } + +export interface IUploadFileSizeAndTypes { + fileTypes: string[] + maxUploadSize: number +} diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index f62e2c56..4451b838 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -21,7 +21,8 @@ import { chatType, IChatMessage, IDepthQueue, - INodeDirectedGraph + INodeDirectedGraph, + IUploadFileSizeAndTypes } from './Interface' import { getNodeModulesPackagePath, @@ -57,7 +58,7 @@ import { Tool } from './database/entities/Tool' import { Assistant } from './database/entities/Assistant' import { ChatflowPool } from './ChatflowPool' import { CachePool } from './CachePool' -import { ICommonObject, IMessage, INodeOptionsValue, handleEscapeCharacters } from 'flowise-components' +import { ICommonObject, IMessage, INodeOptionsValue, INodeParams, handleEscapeCharacters } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey' import { sanitizeMiddleware } from './utils/XSS' @@ -147,7 +148,9 @@ export class App { '/api/v1/node-icon/', '/api/v1/components-credentials-icon/', '/api/v1/chatflows-streaming', + '/api/v1/chatflows-uploads', '/api/v1/openai-assistants-file', + '/api/v1/get-upload-file', '/api/v1/ip' ] this.app.use((req, res, next) => { @@ -464,8 +467,45 @@ export class App { }) if (!chatflow) return res.status(404).send(`Chatflow ${req.params.id} not found`) - const obj = this.shouldAllowUploads(chatflow) - return res.json(obj) + const uploadAllowedNodes = ['OpenAIMultiModalChain', 'OpenAIWhisper'] + + try { + const flowObj = JSON.parse(chatflow.flowData) + let isUploadAllowed = false + const allowances: IUploadFileSizeAndTypes[] = [] + + flowObj.nodes.forEach((node: IReactFlowNode) => { + if (uploadAllowedNodes.indexOf(node.data.type) > -1) { + logger.debug(`[server]: Found Eligible Node ${node.data.type}, Allowing Uploads.`) + isUploadAllowed = true + + const allowance: IUploadFileSizeAndTypes = { + fileTypes: [], + maxUploadSize: 0 + } + + node.data.inputParams.map((param: INodeParams) => { + if (param.name === 'allowedUploadTypes') { + allowance.fileTypes = (param.default as string).split(';') + } + if (param.name === 'maxUploadSize') { + allowance.maxUploadSize = parseInt(param.default ? (param.default as string) : '0') + } + }) + + if (allowance.fileTypes && allowance.maxUploadSize) { + allowances.push(allowance) + } + } + }) + + return res.json({ + isUploadAllowed, + uploadFileSizeAndTypes: allowances + }) + } catch (e) { + return res.status(500).send(e) + } }) // ---------------------------------------- @@ -1058,10 +1098,14 @@ export class App { return res.status(500).send(`Invalid file path`) } const filePath = path.join(getUserHome(), '.flowise', 'gptvision', req.query.chatId as string, req.params.id) - console.log(filePath) - if (!path.isAbsolute(filePath) || !fs.existsSync(filePath)) { + //raise error if file path is not absolute + if (!path.isAbsolute(filePath)) return res.status(500).send(`Invalid file path`) + //raise error if file path contains '..' + if (filePath.includes('..')) return res.status(500).send(`Invalid file path`) + //only return from the .flowise gptvision folder + if (!(filePath.includes('.flowise') && filePath.includes('gptvision') && filePath.includes(req.query.chatId as string))) return res.status(500).send(`Invalid file path`) - } + res.setHeader('Content-Disposition', 'attachment; filename=' + path.basename(filePath)) streamFileToUser(res, filePath) }) @@ -1350,35 +1394,6 @@ export class App { }) } - private uploadAllowedNodes = ['OpenAIMultiModalChain', 'OpenAIWhisper'] - private shouldAllowUploads(result: ChatFlow): any { - const flowObj = JSON.parse(result.flowData) - let allowUploads = false - const allowances: any = [] - flowObj.nodes.forEach((node: IReactFlowNode) => { - if (this.uploadAllowedNodes.indexOf(node.data.type) > -1) { - logger.debug(`[server]: Found Eligible Node ${node.data.type}, Allowing Uploads.`) - allowUploads = true - const allowance: any = {} - node.data.inputParams.map((param: any) => { - if (param.name === 'allowedUploadTypes') { - allowance.allowedTypes = param.default.split(';') - } - if (param.name === 'maxUploadSize') { - allowance.maxUploadSize = parseInt(param.default ? param.default : '0') - } - }) - if (allowance.allowedTypes && allowance.maxUploadSize) { - allowances.push(allowance) - } - } - }) - return { - allowUploads, - allowed: allowances - } - } - /** * Validate API Key * @param {Request} req diff --git a/packages/ui/src/api/chatflows.js b/packages/ui/src/api/chatflows.js index c02ca5cd..586fe183 100644 --- a/packages/ui/src/api/chatflows.js +++ b/packages/ui/src/api/chatflows.js @@ -13,6 +13,7 @@ const updateChatflow = (id, body) => client.put(`/chatflows/${id}`, body) const deleteChatflow = (id) => client.delete(`/chatflows/${id}`) const getIsChatflowStreaming = (id) => client.get(`/chatflows-streaming/${id}`) + const getAllowChatflowUploads = (id) => client.get(`/chatflows-uploads/${id}`) export default { diff --git a/packages/ui/src/ui-component/button/ImageButton.js b/packages/ui/src/ui-component/button/ImageButton.js new file mode 100644 index 00000000..7a10b966 --- /dev/null +++ b/packages/ui/src/ui-component/button/ImageButton.js @@ -0,0 +1,57 @@ +import { styled } from '@mui/material/styles' +import ButtonBase from '@mui/material/ButtonBase' + +export const ImageButton = styled(ButtonBase)(({ theme }) => ({ + position: 'relative', + height: 200, + borderRadius: '10px', + [theme.breakpoints.down('sm')]: { + width: '100% !important', // Overrides inline-style + height: 100 + }, + '&:hover, &.Mui-focusVisible': { + zIndex: 1, + '& .MuiImageBackdrop-root': { + opacity: 0.4 + }, + '& .MuiImageMarked-root': { + opacity: 1 + }, + '& .MuiTypography-root': { + border: '4px solid currentColor' + } + } +})) + +export const ImageSrc = styled('span')({ + position: 'absolute', + borderRadius: '10px', + left: 0, + right: 0, + top: 0, + bottom: 0, + backgroundSize: 'cover', + backgroundPosition: 'center 40%' +}) + +export const ImageBackdrop = styled('span')(({ theme }) => ({ + position: 'absolute', + borderRadius: '10px', + left: 0, + right: 0, + top: 0, + bottom: 0, + backgroundColor: theme.palette.common.black, + opacity: 0.1, + transition: theme.transitions.create('opacity') +})) + +export const ImageMarked = styled('span')(() => ({ + height: 25, + width: 25, + backgroundColor: 'transparent', + position: 'absolute', + top: 'auto', + left: 'auto', + opacity: 0 +})) diff --git a/packages/ui/src/ui-component/cards/StarterPromptsCard.css b/packages/ui/src/ui-component/cards/StarterPromptsCard.css index 85c2d415..028b8b34 100644 --- a/packages/ui/src/ui-component/cards/StarterPromptsCard.css +++ b/packages/ui/src/ui-component/cards/StarterPromptsCard.css @@ -1,6 +1,5 @@ .button-container { position: absolute; - bottom: 0; z-index: 1000; display: flex; overflow-x: auto; diff --git a/packages/ui/src/ui-component/cards/StarterPromptsCard.js b/packages/ui/src/ui-component/cards/StarterPromptsCard.js index 3abd8378..cfec4ba4 100644 --- a/packages/ui/src/ui-component/cards/StarterPromptsCard.js +++ b/packages/ui/src/ui-component/cards/StarterPromptsCard.js @@ -3,9 +3,9 @@ import PropTypes from 'prop-types' import { Chip } from '@mui/material' import './StarterPromptsCard.css' -const StarterPromptsCard = ({ isGrid, starterPrompts, onPromptClick }) => { +const StarterPromptsCard = ({ isGrid, starterPrompts, sx, onPromptClick }) => { return ( - + {starterPrompts.map((sp, index) => ( onPromptClick(sp.prompt, e)} /> ))} @@ -15,7 +15,8 @@ const StarterPromptsCard = ({ isGrid, starterPrompts, onPromptClick }) => { StarterPromptsCard.propTypes = { isGrid: PropTypes.bool, - starterPrompts: PropTypes.arrayOf(PropTypes.string), + starterPrompts: PropTypes.array, + sx: PropTypes.object, onPromptClick: PropTypes.func } diff --git a/packages/ui/src/views/chatmessage/ChatMessage.css b/packages/ui/src/views/chatmessage/ChatMessage.css index 3b0bb9e3..9e7a1857 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.css +++ b/packages/ui/src/views/chatmessage/ChatMessage.css @@ -146,6 +146,16 @@ align-items: center; } +.preview { + position: absolute; + bottom: 0; + z-index: 1000; + display: flex; + overflow-x: auto; + -webkit-overflow-scrolling: touch; /* For momentum scroll on mobile devices */ + scrollbar-width: none; /* For Firefox */ +} + .file-drop-field { position: relative; /* Needed to position the icon correctly */ /* Other styling for the field */ @@ -162,26 +172,6 @@ flex-direction: column; justify-content: center; align-items: center; - z-index: 10; /* Ensure it's above other content */ + z-index: 2000; /* Ensure it's above other content */ border: 2px dashed #0094ff; /* Example style */ } - -.preview-container { - -} - -.preview-card { - border: 2px solid #E7EDF3; - border-radius: 16%; - transition: 0.4s; -} - -.preview-card&:hover { - border-color: #5B9FED; -} - - -.button { - flex: 0 0 auto; /* Don't grow, don't shrink, base width on content */ - margin: 5px; /* Adjust as needed for spacing between buttons */ -} \ No newline at end of file diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index d9003f72..0d969c5e 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useState } from 'react' +import { useState, useRef, useEffect, useCallback } from 'react' import { useSelector } from 'react-redux' import PropTypes from 'prop-types' import socketIOClient from 'socket.io-client' @@ -8,30 +8,33 @@ import rehypeRaw from 'rehype-raw' import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' import axios from 'axios' -import audioUploadSVG from 'assets/images/wave-sound.jpg' import { Box, Button, Card, - CardActions, CardMedia, Chip, CircularProgress, Divider, - Grid, IconButton, InputAdornment, OutlinedInput, Typography } from '@mui/material' import { useTheme } from '@mui/material/styles' -import { IconDownload, IconSend, IconMicrophone, IconPhotoPlus, IconCircleDot } from '@tabler/icons' +import { IconDownload, IconSend, IconMicrophone, IconPhotoPlus, IconCircleDot, IconTrash } from '@tabler/icons' +import robotPNG from 'assets/images/robot.png' +import userPNG from 'assets/images/account.png' +import audioUploadSVG from 'assets/images/wave-sound.jpg' // project import import { CodeBlock } from 'ui-component/markdown/CodeBlock' import { MemoizedReactMarkdown } from 'ui-component/markdown/MemoizedReactMarkdown' import SourceDocDialog from 'ui-component/dialog/SourceDocDialog' +import StarterPromptsCard from 'ui-component/cards/StarterPromptsCard' +import { cancelAudioRecording, startAudioRecording, stopAudioRecording } from './audio-recording' +import { ImageButton, ImageSrc, ImageBackdrop, ImageMarked } from 'ui-component/button/ImageButton' import './ChatMessage.css' import './audio-recording.css' @@ -46,12 +49,14 @@ import useApi from 'hooks/useApi' // Const import { baseURL, maxScroll } from 'store/constant' -import robotPNG from 'assets/images/robot.png' -import userPNG from 'assets/images/account.png' -import StarterPromptsCard from '../../ui-component/cards/StarterPromptsCard' +// Utils import { isValidURL, removeDuplicateURL, setLocalStorageChatflow } from 'utils/genericHelper' -import DeleteIcon from '@mui/icons-material/Delete' -import { cancelAudioRecording, startAudioRecording, stopAudioRecording } from './audio-recording' + +const messageImageStyle = { + width: '128px', + height: '128px', + objectFit: 'cover' +} export const ChatMessage = ({ open, chatflowid, isDialog }) => { const theme = useTheme() @@ -76,13 +81,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { const inputRef = useRef(null) const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow) const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming) + const getAllowChatFlowUploads = useApi(chatflowsApi.getAllowChatflowUploads) const getChatflowConfig = useApi(chatflowsApi.getSpecificChatflow) const [starterPrompts, setStarterPrompts] = useState([]) // drag & drop and file input const fileUploadRef = useRef(null) - const getAllowChatFlowUploads = useApi(chatflowsApi.getAllowChatflowUploads) const [isChatFlowAvailableForUploads, setIsChatFlowAvailableForUploads] = useState(false) const [previews, setPreviews] = useState([]) const [isDragOver, setIsDragOver] = useState(false) @@ -91,20 +96,17 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { const [isRecording, setIsRecording] = useState(false) const [recordingNotSupported, setRecordingNotSupported] = useState(false) - const handleDragOver = (e) => { - if (!isChatFlowAvailableForUploads) { - return - } - e.preventDefault() - } const isFileAllowedForUpload = (file) => { const constraints = getAllowChatFlowUploads.data + /** + * {isUploadAllowed: boolean, uploadFileSizeAndTypes: Array<{ fileTypes: string[], maxUploadSize: number }>} + */ let acceptFile = false - if (constraints.allowUploads) { + if (constraints.isUploadAllowed) { const fileType = file.type const sizeInMB = file.size / 1024 / 1024 - constraints.allowed.map((allowed) => { - if (allowed.allowedTypes.includes(fileType) && sizeInMB <= allowed.maxUploadSize) { + constraints.uploadFileSizeAndTypes.map((allowed) => { + if (allowed.fileTypes.includes(fileType) && sizeInMB <= allowed.maxUploadSize) { acceptFile = true } }) @@ -114,11 +116,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { } return acceptFile } + const handleDrop = async (e) => { if (!isChatFlowAvailableForUploads) { return } e.preventDefault() + e.stopPropagation() setIsDragOver(false) let files = [] if (e.dataTransfer.files.length > 0) { @@ -156,10 +160,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { const newFiles = await Promise.all(files) setPreviews((prevPreviews) => [...prevPreviews, ...newFiles]) - // if (newFiles.length > 0) { - // document.getElementById('messagelist').style.height = '80%' - // } } + if (e.dataTransfer.items) { for (const item of e.dataTransfer.items) { if (item.kind === 'string' && item.type.match('^text/uri-list')) { @@ -191,6 +193,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { } } } + const handleFileChange = async (event) => { const fileObj = event.target.files && event.target.files[0] if (!fileObj) { @@ -247,9 +250,15 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { } } + const handleDragOver = (e) => { + e.preventDefault() + e.stopPropagation() + } + const handleDragEnter = (e) => { if (isChatFlowAvailableForUploads) { e.preventDefault() + e.stopPropagation() setIsDragOver(true) } } @@ -257,34 +266,27 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { const handleDragLeave = (e) => { if (isChatFlowAvailableForUploads) { e.preventDefault() + e.stopPropagation() if (e.originalEvent?.pageX !== 0 || e.originalEvent?.pageY !== 0) { + setIsDragOver(false) return false } - setIsDragOver(false) // Set the drag over state to false when the drag leaves + setIsDragOver(false) } } + const handleDeletePreview = (itemToDelete) => { if (itemToDelete.type === 'file') { URL.revokeObjectURL(itemToDelete.preview) // Clean up for file } setPreviews(previews.filter((item) => item !== itemToDelete)) } + const handleUploadClick = () => { // 👇️ open file input box on click of another element fileUploadRef.current.click() } - const previewStyle = { - width: '128px', - height: '64px', - objectFit: 'fit' // This makes the image cover the area, cropping it if necessary - } - const messageImageStyle = { - width: '128px', - height: '128px', - objectFit: 'cover' // This makes the image cover the area, cropping it if necessary - } - const clearPreviews = () => { // Revoke the data uris to avoid memory leaks previews.forEach((file) => URL.revokeObjectURL(file.preview)) @@ -295,11 +297,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { setIsRecording(true) startAudioRecording(setIsRecording, setRecordingNotSupported) } + const onRecordingCancelled = () => { cancelAudioRecording() setIsRecording(false) setRecordingNotSupported(false) } + const onRecordingStopped = () => { stopAudioRecording(addRecordingToPreviews) setIsRecording(false) @@ -505,7 +509,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { // Get chatflow uploads capability useEffect(() => { if (getAllowChatFlowUploads.data) { - setIsChatFlowAvailableForUploads(getAllowChatFlowUploads.data?.allowUploads ?? false) + setIsChatFlowAvailableForUploads(getAllowChatFlowUploads.data?.isUploadAllowed ?? false) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [getAllowChatFlowUploads.data]) @@ -544,12 +548,18 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { useEffect(() => { let socket if (open && chatflowid) { + // API request getChatmessageApi.request(chatflowid) getIsChatflowStreamingApi.request(chatflowid) getAllowChatFlowUploads.request(chatflowid) getChatflowConfig.request(chatflowid) + + // Scroll to bottom scrollToBottom() + setIsRecording(false) + + // SocketIO socket = socketIOClient(baseURL) socket.on('connect', () => { @@ -584,20 +594,14 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { }, [open, chatflowid]) return ( -
- {isDragOver && getAllowChatFlowUploads.data?.allowUploads && ( + <> + {isDragOver && getAllowChatFlowUploads.data?.isUploadAllowed && ( Drop here to upload - {getAllowChatFlowUploads.data.allowed.map((allowed) => { + {getAllowChatFlowUploads.data.uploadFileSizeAndTypes.map((allowed) => { return ( <> - {allowed.allowedTypes?.join(', ')} + {allowed.fileTypes?.join(', ')} Max Allowed Size: {allowed.maxUploadSize} MB ) @@ -639,7 +643,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { )} )} -
+
{messages && messages.map((message, index) => { @@ -687,6 +697,42 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { })}
)} + {message.fileUploads && message.fileUploads.length > 0 && ( +
+ {message.fileUploads.map((item, index) => { + return ( + <> + {item.mime.startsWith('image/') ? ( + + + + ) : ( + // eslint-disable-next-line jsx-a11y/media-has-caption + + )} + + ) + })} +
+ )}
{/* Messages are being rendered in Markdown format */} { })}
)} - {message.fileUploads && - message.fileUploads.map((item, index) => { - return ( - <> - {item.mime.startsWith('image/') ? ( - - - - ) : ( - // eslint-disable-next-line jsx-a11y/media-has-caption - - )} - - ) - })} {message.sourceDocuments && (
{removeDuplicateURL(message).map((source, index) => { @@ -796,55 +818,79 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
{messages && messages.length === 1 && ( - + 0 ? 70 : 0 }} + starterPrompts={starterPrompts || []} + onPromptClick={handlePromptClick} + isGrid={isDialog} + /> )}
- -
+ +
{previews && previews.length > 0 && ( -
-
- - {previews.map((item, index) => ( - <> - {item.mime.startsWith('image/') ? ( - - - - - handleDeletePreview(item)} size='small'> - - - - - - ) : ( - - - - - handleDeletePreview(item)} size='small'> - - - - - - )} - - ))} - -
-
+ + {previews.map((item, index) => ( + <> + {item.mime.startsWith('image/') ? ( + handleDeletePreview(item)} + > + + + + + + + ) : ( + + + handleDeletePreview(item)} size='small'> + + + + )} + + ))} + )} +
+
{ maxRows={isDialog ? 7 : 2} startAdornment={ isChatFlowAvailableForUploads && ( - + { } /> {isChatFlowAvailableForUploads && ( - + )}
setSourceDialogOpen(false)} /> -
+ ) } diff --git a/packages/ui/src/views/chatmessage/audio-recording.js b/packages/ui/src/views/chatmessage/audio-recording.js index f5cba001..1fbaddc1 100644 --- a/packages/ui/src/views/chatmessage/audio-recording.js +++ b/packages/ui/src/views/chatmessage/audio-recording.js @@ -68,30 +68,39 @@ export function startAudioRecording(onRecordingStart, onUnsupportedBrowser) { //Error handling structure switch (error.name) { case 'AbortError': //error from navigator.mediaDevices.getUserMedia + // eslint-disable-next-line no-console console.log('An AbortError has occurred.') break case 'NotAllowedError': //error from navigator.mediaDevices.getUserMedia + // eslint-disable-next-line no-console console.log('A NotAllowedError has occurred. User might have denied permission.') break case 'NotFoundError': //error from navigator.mediaDevices.getUserMedia + // eslint-disable-next-line no-console console.log('A NotFoundError has occurred.') break case 'NotReadableError': //error from navigator.mediaDevices.getUserMedia + // eslint-disable-next-line no-console console.log('A NotReadableError has occurred.') break case 'SecurityError': //error from navigator.mediaDevices.getUserMedia or from the MediaRecorder.start + // eslint-disable-next-line no-console console.log('A SecurityError has occurred.') break case 'TypeError': //error from navigator.mediaDevices.getUserMedia + // eslint-disable-next-line no-console console.log('A TypeError has occurred.') break case 'InvalidStateError': //error from the MediaRecorder.start + // eslint-disable-next-line no-console console.log('An InvalidStateError has occurred.') break case 'UnknownError': //error from the MediaRecorder.start + // eslint-disable-next-line no-console console.log('An UnknownError has occurred.') break default: + // eslint-disable-next-line no-console console.log('An error occurred with the error name ' + error.name) } }) @@ -113,9 +122,11 @@ export function stopAudioRecording(addRecordingToPreviews) { //Error handling structure switch (error.name) { case 'InvalidStateError': //error from the MediaRecorder.stop + // eslint-disable-next-line no-console console.log('An InvalidStateError has occurred.') break default: + // eslint-disable-next-line no-console console.log('An error occurred with the error name ' + error.name) } })