From 00520a96020519cbb8d6b4b6ff32f6623d01ce3f Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 5 Nov 2025 23:06:00 -0500 Subject: [PATCH] fix: message input dictate Co-Authored-By: Marchotridyo <29671825+acomarcho@users.noreply.github.com> --- src/lib/components/chat/MessageInput.svelte | 1371 ++++++++--------- .../chat/MessageInput/VoiceRecording.svelte | 1 + 2 files changed, 684 insertions(+), 688 deletions(-) diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index 13d1c8257e..71130392c6 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -1019,7 +1019,7 @@ }} /> - {#if recording} +
{ @@ -1034,7 +1034,7 @@ recording = false; await tick(); - await insertTextAtCursor(text); + await insertTextAtCursor(`${text}`); await tick(); document.getElementById('chat-input')?.focus(); @@ -1043,390 +1043,427 @@ } }} /> - {:else} -
{ - // check if selectedModels support image input - dispatch('submit', prompt); - }} - > -
+ { + // check if selectedModels support image input + dispatch('submit', prompt); + }} + > + +
+ {#if atSelectedModel !== undefined} +
+
+
+ model profile model.id === atSelectedModel.id)?.info?.meta + ?.profile_image_url ?? + ($i18n.language === 'dg-DG' + ? `${WEBUI_BASE_URL}/doge.png` + : `${WEBUI_BASE_URL}/static/favicon.png`)} + /> +
+ {atSelectedModel.name}
+
+ +
- {/if} +
+ {/if} - {#if files.length > 0} -
- {#each files as file, fileIdx} - {#if file.type === 'image'} -
-
- - {#if atSelectedModel ? visionCapableModels.length === 0 : selectedModels.length !== visionCapableModels.length} - !visionCapableModels.includes(id)) - .join(', ') - })} - > - - - {/if} -
-
- -
+ + {/if}
- {:else} - { - // Remove from UI state - files.splice(fileIdx, 1); - files = files; +
+ +
+
+ {:else} + { + // Remove from UI state + files.splice(fileIdx, 1); + files = files; + }} + on:click={() => { + console.log(file); + }} + /> + {/if} + {/each} +
+ {/if} + +
+
+ {#if suggestions} + {#key $settings?.richTextInput ?? true} + {#key $settings?.showFormattingToolbar ?? false} + { + prompt = e.md; + command = getCommand(); }} - on:click={() => { - console.log(file); + json={true} + richText={$settings?.richTextInput ?? true} + messageInput={true} + showFormattingToolbar={$settings?.showFormattingToolbar ?? false} + floatingMenuPlacement={'top-start'} + insertPromptAsRichText={$settings?.insertPromptAsRichText ?? false} + shiftEnter={!($settings?.ctrlEnterToSend ?? false) && + !$mobile && + !( + 'ontouchstart' in window || + navigator.maxTouchPoints > 0 || + navigator.msMaxTouchPoints > 0 + )} + placeholder={placeholder ? placeholder : $i18n.t('Send a Message')} + largeTextAsFile={($settings?.largeTextAsFile ?? false) && !shiftKey} + autocomplete={$config?.features?.enable_autocomplete_generation && + ($settings?.promptAutocomplete ?? false)} + generateAutoCompletion={async (text) => { + if (selectedModelIds.length === 0 || !selectedModelIds.at(0)) { + toast.error($i18n.t('Please select a model first.')); + } + + const res = await generateAutoCompletion( + localStorage.token, + selectedModelIds.at(0), + text, + history?.currentId + ? createMessagesList(history, history.currentId) + : null + ).catch((error) => { + console.log(error); + + return null; + }); + + console.log(res); + return res; }} - /> - {/if} - {/each} -
- {/if} + {suggestions} + oncompositionstart={() => (isComposing = true)} + oncompositionend={(e) => { + compositionEndedAt = e.timeStamp; + isComposing = false; + }} + on:keydown={async (e) => { + e = e.detail.event; -
-
- {#if suggestions} - {#key $settings?.richTextInput ?? true} - {#key $settings?.showFormattingToolbar ?? false} - { - prompt = e.md; - command = getCommand(); - }} - json={true} - richText={$settings?.richTextInput ?? true} - messageInput={true} - showFormattingToolbar={$settings?.showFormattingToolbar ?? false} - floatingMenuPlacement={'top-start'} - insertPromptAsRichText={$settings?.insertPromptAsRichText ?? false} - shiftEnter={!($settings?.ctrlEnterToSend ?? false) && - !$mobile && - !( - 'ontouchstart' in window || - navigator.maxTouchPoints > 0 || - navigator.msMaxTouchPoints > 0 - )} - placeholder={placeholder ? placeholder : $i18n.t('Send a Message')} - largeTextAsFile={($settings?.largeTextAsFile ?? false) && !shiftKey} - autocomplete={$config?.features?.enable_autocomplete_generation && - ($settings?.promptAutocomplete ?? false)} - generateAutoCompletion={async (text) => { - if (selectedModelIds.length === 0 || !selectedModelIds.at(0)) { - toast.error($i18n.t('Please select a model first.')); - } + const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac + const suggestionsContainerElement = + document.getElementById('suggestions-container'); - const res = await generateAutoCompletion( - localStorage.token, - selectedModelIds.at(0), - text, - history?.currentId - ? createMessagesList(history, history.currentId) - : null - ).catch((error) => { - console.log(error); + if (e.key === 'Escape') { + stopResponse(); + } - return null; - }); + if (prompt === '' && e.key == 'ArrowUp') { + e.preventDefault(); - console.log(res); - return res; - }} - {suggestions} - oncompositionstart={() => (isComposing = true)} - oncompositionend={(e) => { - compositionEndedAt = e.timeStamp; - isComposing = false; - }} - on:keydown={async (e) => { - e = e.detail.event; + const userMessageElement = [ + ...document.getElementsByClassName('user-message') + ]?.at(-1); - const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac - const suggestionsContainerElement = - document.getElementById('suggestions-container'); - - if (e.key === 'Escape') { - stopResponse(); - } - - if (prompt === '' && e.key == 'ArrowUp') { - e.preventDefault(); - - const userMessageElement = [ - ...document.getElementsByClassName('user-message') + if (userMessageElement) { + userMessageElement.scrollIntoView({ block: 'center' }); + const editButton = [ + ...document.getElementsByClassName('edit-user-message-button') ]?.at(-1); - if (userMessageElement) { - userMessageElement.scrollIntoView({ block: 'center' }); - const editButton = [ - ...document.getElementsByClassName('edit-user-message-button') - ]?.at(-1); - - editButton?.click(); - } + editButton?.click(); } + } - if (!suggestionsContainerElement) { - if ( - !$mobile || - !( - 'ontouchstart' in window || - navigator.maxTouchPoints > 0 || - navigator.msMaxTouchPoints > 0 - ) - ) { - if (inOrNearComposition(e)) { - return; - } + if (!suggestionsContainerElement) { + if ( + !$mobile || + !( + 'ontouchstart' in window || + navigator.maxTouchPoints > 0 || + navigator.msMaxTouchPoints > 0 + ) + ) { + if (inOrNearComposition(e)) { + return; + } - // Uses keyCode '13' for Enter key for chinese/japanese keyboards. - // - // Depending on the user's settings, it will send the message - // either when Enter is pressed or when Ctrl+Enter is pressed. - const enterPressed = - ($settings?.ctrlEnterToSend ?? false) - ? (e.key === 'Enter' || e.keyCode === 13) && isCtrlPressed - : (e.key === 'Enter' || e.keyCode === 13) && !e.shiftKey; + // Uses keyCode '13' for Enter key for chinese/japanese keyboards. + // + // Depending on the user's settings, it will send the message + // either when Enter is pressed or when Ctrl+Enter is pressed. + const enterPressed = + ($settings?.ctrlEnterToSend ?? false) + ? (e.key === 'Enter' || e.keyCode === 13) && isCtrlPressed + : (e.key === 'Enter' || e.keyCode === 13) && !e.shiftKey; - if (enterPressed) { - e.preventDefault(); - if (prompt !== '' || files.length > 0) { - dispatch('submit', prompt); - } + if (enterPressed) { + e.preventDefault(); + if (prompt !== '' || files.length > 0) { + dispatch('submit', prompt); } } } + } - if (e.key === 'Escape') { - console.log('Escape'); - atSelectedModel = undefined; - selectedToolIds = []; - selectedFilterIds = []; + if (e.key === 'Escape') { + console.log('Escape'); + atSelectedModel = undefined; + selectedToolIds = []; + selectedFilterIds = []; - webSearchEnabled = false; - imageGenerationEnabled = false; - codeInterpreterEnabled = false; - } - }} - on:paste={async (e) => { - e = e.detail.event; - console.log(e); + webSearchEnabled = false; + imageGenerationEnabled = false; + codeInterpreterEnabled = false; + } + }} + on:paste={async (e) => { + e = e.detail.event; + console.log(e); - const clipboardData = e.clipboardData || window.clipboardData; + const clipboardData = e.clipboardData || window.clipboardData; - if (clipboardData && clipboardData.items) { - for (const item of clipboardData.items) { - if (item.type.indexOf('image') !== -1) { - const blob = item.getAsFile(); - const reader = new FileReader(); + if (clipboardData && clipboardData.items) { + for (const item of clipboardData.items) { + if (item.type.indexOf('image') !== -1) { + const blob = item.getAsFile(); + const reader = new FileReader(); - reader.onload = function (e) { - files = [ - ...files, - { - type: 'image', - url: `${e.target.result}` - } - ]; - }; - - reader.readAsDataURL(blob); - } else if (item?.kind === 'file') { - const file = item.getAsFile(); - if (file) { - const _files = [file]; - await inputFilesHandler(_files); - e.preventDefault(); - } - } else if (item.type === 'text/plain') { - if (($settings?.largeTextAsFile ?? false) && !shiftKey) { - const text = clipboardData.getData('text/plain'); - - if (text.length > PASTED_TEXT_CHARACTER_LIMIT) { - e.preventDefault(); - const blob = new Blob([text], { type: 'text/plain' }); - const file = new File( - [blob], - `Pasted_Text_${Date.now()}.txt`, - { - type: 'text/plain' - } - ); - - await uploadFileHandler(file, true); + reader.onload = function (e) { + files = [ + ...files, + { + type: 'image', + url: `${e.target.result}` } + ]; + }; + + reader.readAsDataURL(blob); + } else if (item?.kind === 'file') { + const file = item.getAsFile(); + if (file) { + const _files = [file]; + await inputFilesHandler(_files); + e.preventDefault(); + } + } else if (item.type === 'text/plain') { + if (($settings?.largeTextAsFile ?? false) && !shiftKey) { + const text = clipboardData.getData('text/plain'); + + if (text.length > PASTED_TEXT_CHARACTER_LIMIT) { + e.preventDefault(); + const blob = new Blob([text], { type: 'text/plain' }); + const file = new File( + [blob], + `Pasted_Text_${Date.now()}.txt`, + { + type: 'text/plain' + } + ); + + await uploadFileHandler(file, true); } } } } - }} - /> - {/key} + } + }} + /> {/key} - {/if} -
+ {/key} + {/if}
+
-
-
- +
+ { + filesInputElement.click(); + }} + uploadGoogleDriveHandler={async () => { + try { + const fileData = await createPicker(); + if (fileData) { + const file = new File([fileData.blob], fileData.name, { + type: fileData.blob.type + }); + await uploadFileHandler(file); + } else { + console.log('No file was selected from Google Drive'); + } + } catch (error) { + console.error('Google Drive Error:', error); + toast.error( + $i18n.t('Error accessing Google Drive: {{error}}', { + error: error.message + }) + ); + } + }} + uploadOneDriveHandler={async (authorityType) => { + try { + const fileData = await pickAndDownloadFile(authorityType); + if (fileData) { + const file = new File([fileData.blob], fileData.name, { + type: fileData.blob.type || 'application/octet-stream' + }); + await uploadFileHandler(file); + } else { + console.log('No file was selected from OneDrive'); + } + } catch (error) { + console.error('OneDrive Error:', error); + } + }} + onUpload={async (e) => { + dispatch('upload', e); + }} + onClose={async () => { + await tick(); + + const chatInput = document.getElementById('chat-input'); + chatInput?.focus(); + }} + > +
+ +
+
+ + {#if showWebSearchButton || showImageGenerationButton || showCodeInterpreterButton || showToolsButton || (toggleFilters && toggleFilters.length > 0)} +
+ + { - filesInputElement.click(); - }} - uploadGoogleDriveHandler={async () => { - try { - const fileData = await createPicker(); - if (fileData) { - const file = new File([fileData.blob], fileData.name, { - type: fileData.blob.type - }); - await uploadFileHandler(file); - } else { - console.log('No file was selected from Google Drive'); - } - } catch (error) { - console.error('Google Drive Error:', error); - toast.error( - $i18n.t('Error accessing Google Drive: {{error}}', { - error: error.message - }) - ); - } - }} - uploadOneDriveHandler={async (authorityType) => { - try { - const fileData = await pickAndDownloadFile(authorityType); - if (fileData) { - const file = new File([fileData.blob], fileData.name, { - type: fileData.blob.type || 'application/octet-stream' - }); - await uploadFileHandler(file); - } else { - console.log('No file was selected from OneDrive'); - } - } catch (error) { - console.error('OneDrive Error:', error); - } - }} - onUpload={async (e) => { - dispatch('upload', e); + {toggleFilters} + {showWebSearchButton} + {showImageGenerationButton} + {showCodeInterpreterButton} + bind:selectedToolIds + bind:selectedFilterIds + bind:webSearchEnabled + bind:imageGenerationEnabled + bind:codeInterpreterEnabled + closeOnOutsideClick={integrationsMenuCloseOnOutsideClick} + onShowValves={(e) => { + const { type, id } = e; + selectedValvesType = type; + selectedValvesItemId = id; + showValvesModal = true; + integrationsMenuCloseOnOutsideClick = false; }} onClose={async () => { await tick(); @@ -1436,376 +1473,334 @@ }} >
- +
- - - {#if showWebSearchButton || showImageGenerationButton || showCodeInterpreterButton || showToolsButton || (toggleFilters && toggleFilters.length > 0)} -
- - { - const { type, id } = e; - selectedValvesType = type; - selectedValvesItemId = id; - showValvesModal = true; - integrationsMenuCloseOnOutsideClick = false; - }} - onClose={async () => { - await tick(); - - const chatInput = document.getElementById('chat-input'); - chatInput?.focus(); - }} - > -
- -
-
- {/if} - - {#if selectedModelIds.length === 1 && $models.find((m) => m.id === selectedModelIds[0])?.has_user_valves} -
- - - -
- {/if} + + {/if} + {#if selectedModelIds.length === 1 && $models.find((m) => m.id === selectedModelIds[0])?.has_user_valves}
- {#if (selectedToolIds ?? []).length > 0} - - - - {/if} - - {#each selectedFilterIds as filterId} - {@const filter = toggleFilters.find((f) => f.id === filterId)} - {#if filter} - - - - {/if} - {/each} - - {#if webSearchEnabled} - - - - {/if} - - {#if imageGenerationEnabled} - - - - {/if} - - {#if codeInterpreterEnabled} - - - - {/if} -
-
- -
- {#if (!history?.currentId || history.messages[history.currentId]?.done == true) && ($_user?.role === 'admin' || ($_user?.permissions?.chat?.stt ?? true))} - - + + +
+ {/if} + +
+ {#if (selectedToolIds ?? []).length > 0} + + {/if} - {#if (taskIds && taskIds.length > 0) || (history.currentId && history.messages[history.currentId]?.done != true) || generating} -
- + {#each selectedFilterIds as filterId} + {@const filter = toggleFilters.find((f) => f.id === filterId)} + {#if filter} + - -
- {:else if prompt === '' && files.length === 0 && ($_user?.role === 'admin' || ($_user?.permissions?.chat?.call ?? true))} -
- - - -
- {:else} -
- - - -
+ {/if} + {/each} + + {#if webSearchEnabled} + + + + {/if} + + {#if imageGenerationEnabled} + + + + {/if} + + {#if codeInterpreterEnabled} + + + {/if}
-
- {#if $config?.license_metadata?.input_footer} -
- {@html DOMPurify.sanitize(marked($config?.license_metadata?.input_footer))} +
+ {#if (!history?.currentId || history.messages[history.currentId]?.done == true) && ($_user?.role === 'admin' || ($_user?.permissions?.chat?.stt ?? true))} + + + + + {/if} + + {#if (taskIds && taskIds.length > 0) || (history.currentId && history.messages[history.currentId]?.done != true) || generating} +
+ + + +
+ {:else if prompt === '' && files.length === 0 && ($_user?.role === 'admin' || ($_user?.permissions?.chat?.call ?? true))} +
+ + + + +
+ {:else} +
+ + + +
+ {/if}
- {:else} -
- {/if} - - {/if} +
+
+ + {#if $config?.license_metadata?.input_footer} +
+ {@html DOMPurify.sanitize(marked($config?.license_metadata?.input_footer))} +
+ {:else} +
+ {/if} +
diff --git a/src/lib/components/chat/MessageInput/VoiceRecording.svelte b/src/lib/components/chat/MessageInput/VoiceRecording.svelte index 7feef174f4..526421daa7 100644 --- a/src/lib/components/chat/MessageInput/VoiceRecording.svelte +++ b/src/lib/components/chat/MessageInput/VoiceRecording.svelte @@ -335,6 +335,7 @@ stopDurationCounter(); audioChunks = []; + visualizerData = Array(VISUALIZER_BUFFER_LENGTH).fill(0); if (stream) { const tracks = stream.getTracks();