{#if loaded}
{#if autoScroll === false && history?.currentId}
{ autoScroll = true; scrollToBottom(); }} >
{/if}
{#if atSelectedModel !== undefined || selectedToolIds.length > 0 || webSearchEnabled || ($settings?.webSearch ?? false) === 'always' || imageGenerationEnabled || codeInterpreterEnabled}
{#if selectedToolIds.length > 0}
{#each selectedToolIds.map((id) => { return $tools ? $tools.find((t) => t.id === id) : { id: id, name: id }; }) as tool, toolIdx (toolIdx)}
{tool.name}
{#if toolIdx !== selectedToolIds.length - 1}
,
{/if} {/each}
{/if} {#if atSelectedModel !== undefined}
model.id === atSelectedModel.id)?.info?.meta ?.profile_image_url ?? ($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)} />
Talking to
{atSelectedModel.name}
{ atSelectedModel = undefined; }} >
{/if}
{/if}
{ dispatch('upload', e.detail); }} on:select={(e) => { const data = e.detail; if (data?.type === 'model') { atSelectedModel = data.data; } const chatInputElement = document.getElementById('chat-input'); chatInputElement?.focus(); }} />
{ if (inputFiles && inputFiles.length > 0) { const _inputFiles = Array.from(inputFiles); inputFilesHandler(_inputFiles); } else { toast.error($i18n.t(`File not found.`)); } filesInputElement.value = ''; }} /> {#if recording}
{ recording = false; await tick(); document.getElementById('chat-input')?.focus(); }} on:confirm={async (e) => { const { text, filename } = e.detail; prompt = `${prompt}${text} `; recording = false; await tick(); document.getElementById('chat-input')?.focus(); if ($settings?.speechAutoSend ?? false) { dispatch('submit', prompt); } }} /> {:else}
{ // check if selectedModels support image input dispatch('submit', prompt); }} >
{#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}
{ files.splice(fileIdx, 1); files = files; }} >
{:else}
{ if (file.type !== 'collection' && !file?.collection) { if (file.id) { // This will handle both file deletion and Chroma cleanup await deleteFileById(localStorage.token, file.id); } } // Remove from UI state files.splice(fileIdx, 1); files = files; }} on:click={() => { console.log(file); }} /> {/if} {/each}
{/if}
{#if $settings?.richTextInput ?? true}
0 || navigator.msMaxTouchPoints > 0 ))} placeholder={placeholder ? placeholder : $i18n.t('Send a Message')} largeTextAsFile={$settings?.largeTextAsFile ?? false} 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; }} oncompositionstart={() => (isComposing = true)} oncompositionend={() => (isComposing = false)} on:keydown={async (e) => { e = e.detail.event; const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac const commandsContainerElement = document.getElementById('commands-container'); if (e.key === 'Escape') { stopResponse(); } // Command/Ctrl + Shift + Enter to submit a message pair if (isCtrlPressed && e.key === 'Enter' && e.shiftKey) { e.preventDefault(); createMessagePair(prompt); } // Check if Ctrl + R is pressed if (prompt === '' && isCtrlPressed && e.key.toLowerCase() === 'r') { e.preventDefault(); console.log('regenerate'); const regenerateButton = [ ...document.getElementsByClassName('regenerate-response-button') ]?.at(-1); regenerateButton?.click(); } if (prompt === '' && e.key == 'ArrowUp') { e.preventDefault(); const userMessageElement = [ ...document.getElementsByClassName('user-message') ]?.at(-1); if (userMessageElement) { userMessageElement.scrollIntoView({ block: 'center' }); const editButton = [ ...document.getElementsByClassName('edit-user-message-button') ]?.at(-1); editButton?.click(); } } if (commandsContainerElement) { if (commandsContainerElement && e.key === 'ArrowUp') { e.preventDefault(); commandsElement.selectUp(); const commandOptionButton = [ ...document.getElementsByClassName('selected-command-option-button') ]?.at(-1); commandOptionButton.scrollIntoView({ block: 'center' }); } if (commandsContainerElement && e.key === 'ArrowDown') { e.preventDefault(); commandsElement.selectDown(); const commandOptionButton = [ ...document.getElementsByClassName('selected-command-option-button') ]?.at(-1); commandOptionButton.scrollIntoView({ block: 'center' }); } if (commandsContainerElement && e.key === 'Tab') { e.preventDefault(); const commandOptionButton = [ ...document.getElementsByClassName('selected-command-option-button') ]?.at(-1); commandOptionButton?.click(); } if (commandsContainerElement && e.key === 'Enter') { e.preventDefault(); const commandOptionButton = [ ...document.getElementsByClassName('selected-command-option-button') ]?.at(-1); if (commandOptionButton) { commandOptionButton?.click(); } else { document.getElementById('send-message-button')?.click(); } } } else { if ( !$mobile || !( 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0 ) ) { if (isComposing) { 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; if (enterPressed) { e.preventDefault(); if (prompt !== '' || files.length > 0) { dispatch('submit', prompt); } } } } if (e.key === 'Escape') { console.log('Escape'); atSelectedModel = undefined; selectedToolIds = []; webSearchEnabled = false; imageGenerationEnabled = false; } }} on:paste={async (e) => { e = e.detail.event; console.log(e); 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(); reader.onload = function (e) { files = [ ...files, { type: 'image', url: `${e.target.result}` } ]; }; reader.readAsDataURL(blob); } else if (item.type === 'text/plain') { if ($settings?.largeTextAsFile ?? false) { 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); } } } } } }} />
{:else}
(isComposing = true)} on:compositionend={() => (isComposing = false)} on:keydown={async (e) => { const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac const commandsContainerElement = document.getElementById('commands-container'); if (e.key === 'Escape') { stopResponse(); } // Command/Ctrl + Shift + Enter to submit a message pair if (isCtrlPressed && e.key === 'Enter' && e.shiftKey) { e.preventDefault(); createMessagePair(prompt); } // Check if Ctrl + R is pressed if (prompt === '' && isCtrlPressed && e.key.toLowerCase() === 'r') { e.preventDefault(); console.log('regenerate'); const regenerateButton = [ ...document.getElementsByClassName('regenerate-response-button') ]?.at(-1); regenerateButton?.click(); } if (prompt === '' && e.key == 'ArrowUp') { e.preventDefault(); const userMessageElement = [ ...document.getElementsByClassName('user-message') ]?.at(-1); const editButton = [ ...document.getElementsByClassName('edit-user-message-button') ]?.at(-1); console.log(userMessageElement); userMessageElement.scrollIntoView({ block: 'center' }); editButton?.click(); } if (commandsContainerElement) { if (commandsContainerElement && e.key === 'ArrowUp') { e.preventDefault(); commandsElement.selectUp(); const commandOptionButton = [ ...document.getElementsByClassName('selected-command-option-button') ]?.at(-1); commandOptionButton.scrollIntoView({ block: 'center' }); } if (commandsContainerElement && e.key === 'ArrowDown') { e.preventDefault(); commandsElement.selectDown(); const commandOptionButton = [ ...document.getElementsByClassName('selected-command-option-button') ]?.at(-1); commandOptionButton.scrollIntoView({ block: 'center' }); } if (commandsContainerElement && e.key === 'Enter') { e.preventDefault(); const commandOptionButton = [ ...document.getElementsByClassName('selected-command-option-button') ]?.at(-1); if (e.shiftKey) { prompt = `${prompt}\n`; } else if (commandOptionButton) { commandOptionButton?.click(); } else { document.getElementById('send-message-button')?.click(); } } if (commandsContainerElement && e.key === 'Tab') { e.preventDefault(); const commandOptionButton = [ ...document.getElementsByClassName('selected-command-option-button') ]?.at(-1); commandOptionButton?.click(); } } else { if ( !$mobile || !( 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0 ) ) { if (isComposing) { return; } // Prevent Enter key from creating a new line const isCtrlPressed = e.ctrlKey || e.metaKey; const enterPressed = ($settings?.ctrlEnterToSend ?? false) ? (e.key === 'Enter' || e.keyCode === 13) && isCtrlPressed : (e.key === 'Enter' || e.keyCode === 13) && !e.shiftKey; console.log('Enter pressed:', enterPressed); if (enterPressed) { e.preventDefault(); } // Submit the prompt when Enter key is pressed if ((prompt !== '' || files.length > 0) && enterPressed) { dispatch('submit', prompt); } } } if (e.key === 'Tab') { const words = findWordIndices(prompt); if (words.length > 0) { const word = words.at(0); const fullPrompt = prompt; prompt = prompt.substring(0, word?.endIndex + 1); await tick(); e.target.scrollTop = e.target.scrollHeight; prompt = fullPrompt; await tick(); e.preventDefault(); e.target.setSelectionRange(word?.startIndex, word.endIndex + 1); } e.target.style.height = ''; e.target.style.height = Math.min(e.target.scrollHeight, 320) + 'px'; } if (e.key === 'Escape') { console.log('Escape'); atSelectedModel = undefined; selectedToolIds = []; webSearchEnabled = false; imageGenerationEnabled = false; } }} rows="1" on:input={async (e) => { e.target.style.height = ''; e.target.style.height = Math.min(e.target.scrollHeight, 320) + 'px'; }} on:focus={async (e) => { e.target.style.height = ''; e.target.style.height = Math.min(e.target.scrollHeight, 320) + 'px'; }} on:paste={async (e) => { 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(); reader.onload = function (e) { files = [ ...files, { type: 'image', url: `${e.target.result}` } ]; }; reader.readAsDataURL(blob); } else if (item.type === 'text/plain') { if ($settings?.largeTextAsFile ?? false) { 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); } } } } } }} /> {/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 () => { try { const fileData = await pickAndDownloadFile(); 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); } }} onClose={async () => { await tick(); const chatInput = document.getElementById('chat-input'); chatInput?.focus(); }} >
{#if $_user} {#if $config?.features?.enable_web_search && ($_user.role === 'admin' || $_user?.permissions?.features?.web_search)}
(webSearchEnabled = !webSearchEnabled)} type="button" class="px-1.5 @xl:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden {webSearchEnabled || ($settings?.webSearch ?? false) === 'always' ? 'bg-blue-100 dark:bg-blue-500/20 text-blue-500 dark:text-blue-400' : 'bg-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'}" >
{$i18n.t('Web Search')}
{/if} {#if $config?.features?.enable_image_generation && ($_user.role === 'admin' || $_user?.permissions?.features?.image_generation)}
(imageGenerationEnabled = !imageGenerationEnabled)} type="button" class="px-1.5 @xl:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden {imageGenerationEnabled ? 'bg-gray-100 dark:bg-gray-500/20 text-gray-600 dark:text-gray-400' : 'bg-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 '}" >
{$i18n.t('Image')}
{/if} {#if $config?.features?.enable_code_interpreter && ($_user.role === 'admin' || $_user?.permissions?.features?.code_interpreter)}
(codeInterpreterEnabled = !codeInterpreterEnabled)} type="button" class="px-1.5 @xl:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden {codeInterpreterEnabled ? 'bg-gray-100 dark:bg-gray-500/20 text-gray-600 dark:text-gray-400' : 'bg-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 '}" >
{$i18n.t('Code Interpreter')}
{/if} {/if}
{#if toolServers.length > 0}
{ showToolServers = !showToolServers; }} >
{toolServers.length}
{/if} {#if !history?.currentId || history.messages[history.currentId]?.done == true}
{ try { let stream = await navigator.mediaDevices .getUserMedia({ audio: true }) .catch(function (err) { toast.error( $i18n.t( `Permission denied when accessing microphone: {{error}}`, { error: err } ) ); return null; }); if (stream) { recording = true; const tracks = stream.getTracks(); tracks.forEach((track) => track.stop()); } stream = null; } catch { toast.error($i18n.t('Permission denied when accessing microphone')); } }} aria-label="Voice Input" >
{/if} {#if !history.currentId || history.messages[history.currentId]?.done == true} {#if prompt === '' && files.length === 0}
{ if (selectedModels.length > 1) { toast.error($i18n.t('Select only one model to call')); return; } if ($config.audio.stt.engine === 'web') { toast.error( $i18n.t( 'Call feature is not supported when using Web STT engine' ) ); return; } // check if user has access to getUserMedia try { let stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // If the user grants the permission, proceed to show the call overlay if (stream) { const tracks = stream.getTracks(); tracks.forEach((track) => track.stop()); } stream = null; if ($settings.audio?.tts?.engine === 'browser-kokoro') { // If the user has not initialized the TTS worker, initialize it if (!$TTSWorker) { await TTSWorker.set( new KokoroWorker({ dtype: $settings.audio?.tts?.engineConfig?.dtype ?? 'fp32' }) ); await $TTSWorker.init(); } } showCallOverlay.set(true); showControls.set(true); } catch (err) { // If the user denies the permission or an error occurs, show an error message toast.error( $i18n.t('Permission denied when accessing media devices') ); } }} aria-label="Call" >
{:else}
{/if} {:else}
{ stopResponse(); }} >
{/if}
{/if}
{/if}