mirror of
				https://github.com/open-webui/open-webui
				synced 2025-06-26 18:26:48 +00:00 
			
		
		
		
	feat: rich text input for chat
This commit is contained in:
		
							parent
							
								
									5e96922eba
								
							
						
					
					
						commit
						f46b95300b
					
				| @ -30,7 +30,7 @@ describe('Settings', () => { | ||||
| 			// Select the first model
 | ||||
| 			cy.get('button[aria-label="model-item"]').first().click(); | ||||
| 			// Type a message
 | ||||
| 			cy.get('#chat-textarea').type('Hi, what can you do? A single sentence only please.', { | ||||
| 			cy.get('#chat-input').type('Hi, what can you do? A single sentence only please.', { | ||||
| 				force: true | ||||
| 			}); | ||||
| 			// Send the message
 | ||||
| @ -50,7 +50,7 @@ describe('Settings', () => { | ||||
| 			// Select the first model
 | ||||
| 			cy.get('button[aria-label="model-item"]').first().click(); | ||||
| 			// Type a message
 | ||||
| 			cy.get('#chat-textarea').type('Hi, what can you do? A single sentence only please.', { | ||||
| 			cy.get('#chat-input').type('Hi, what can you do? A single sentence only please.', { | ||||
| 				force: true | ||||
| 			}); | ||||
| 			// Send the message
 | ||||
| @ -85,7 +85,7 @@ describe('Settings', () => { | ||||
| 			// Select the first model
 | ||||
| 			cy.get('button[aria-label="model-item"]').first().click(); | ||||
| 			// Type a message
 | ||||
| 			cy.get('#chat-textarea').type('Hi, what can you do? A single sentence only please.', { | ||||
| 			cy.get('#chat-input').type('Hi, what can you do? A single sentence only please.', { | ||||
| 				force: true | ||||
| 			}); | ||||
| 			// Send the message
 | ||||
|  | ||||
| @ -125,7 +125,7 @@ | ||||
| 				loaded = true; | ||||
| 
 | ||||
| 				window.setTimeout(() => scrollToBottom(), 0); | ||||
| 				const chatInput = document.getElementById('chat-textarea'); | ||||
| 				const chatInput = document.getElementById('chat-input'); | ||||
| 				chatInput?.focus(); | ||||
| 			} else { | ||||
| 				await goto('/'); | ||||
| @ -264,7 +264,7 @@ | ||||
| 		if (event.data.type === 'input:prompt') { | ||||
| 			console.debug(event.data.text); | ||||
| 
 | ||||
| 			const inputElement = document.getElementById('chat-textarea'); | ||||
| 			const inputElement = document.getElementById('chat-input'); | ||||
| 
 | ||||
| 			if (inputElement) { | ||||
| 				prompt = event.data.text; | ||||
| @ -327,7 +327,7 @@ | ||||
| 			} | ||||
| 		}); | ||||
| 
 | ||||
| 		const chatInput = document.getElementById('chat-textarea'); | ||||
| 		const chatInput = document.getElementById('chat-input'); | ||||
| 		chatInput?.focus(); | ||||
| 
 | ||||
| 		chats.subscribe(() => {}); | ||||
| @ -501,7 +501,7 @@ | ||||
| 			settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}')); | ||||
| 		} | ||||
| 
 | ||||
| 		const chatInput = document.getElementById('chat-textarea'); | ||||
| 		const chatInput = document.getElementById('chat-input'); | ||||
| 		setTimeout(() => chatInput?.focus(), 0); | ||||
| 	}; | ||||
| 
 | ||||
| @ -799,7 +799,7 @@ | ||||
| 			); | ||||
| 		} else { | ||||
| 			// Reset chat input textarea | ||||
| 			const chatTextAreaElement = document.getElementById('chat-textarea'); | ||||
| 			const chatTextAreaElement = document.getElementById('chat-input'); | ||||
| 
 | ||||
| 			if (chatTextAreaElement) { | ||||
| 				chatTextAreaElement.value = ''; | ||||
| @ -841,6 +841,11 @@ | ||||
| 
 | ||||
| 			// Wait until history/message have been updated | ||||
| 			await tick(); | ||||
| 
 | ||||
| 			// focus on chat input | ||||
| 			const chatInput = document.getElementById('chat-input'); | ||||
| 			chatInput?.focus(); | ||||
| 
 | ||||
| 			_responses = await sendPrompt(userPrompt, userMessageId, { newChat: true }); | ||||
| 		} | ||||
| 
 | ||||
|  | ||||
| @ -29,6 +29,7 @@ | ||||
| 	import FilesOverlay from './MessageInput/FilesOverlay.svelte'; | ||||
| 	import Commands from './MessageInput/Commands.svelte'; | ||||
| 	import XMark from '../icons/XMark.svelte'; | ||||
| 	import RichTextInput from '../common/RichTextInput.svelte'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| @ -53,8 +54,8 @@ | ||||
| 	let recording = false; | ||||
| 
 | ||||
| 	let chatTextAreaElement: HTMLTextAreaElement; | ||||
| 	let chatInputContainerElement; | ||||
| 	let filesInputElement; | ||||
| 
 | ||||
| 	let commandsElement; | ||||
| 
 | ||||
| 	let inputFiles; | ||||
| @ -213,7 +214,10 @@ | ||||
| 	}; | ||||
| 
 | ||||
| 	onMount(() => { | ||||
| 		window.setTimeout(() => chatTextAreaElement?.focus(), 0); | ||||
| 		window.setTimeout(() => { | ||||
| 			const chatInput = document.getElementById('chat-input'); | ||||
| 			chatInput?.focus(); | ||||
| 		}, 0); | ||||
| 
 | ||||
| 		window.addEventListener('keydown', handleKeyDown); | ||||
| 
 | ||||
| @ -351,7 +355,7 @@ | ||||
| 							recording = false; | ||||
| 
 | ||||
| 							await tick(); | ||||
| 							document.getElementById('chat-textarea')?.focus(); | ||||
| 							document.getElementById('chat-input')?.focus(); | ||||
| 						}} | ||||
| 						on:confirm={async (e) => { | ||||
| 							const response = e.detail; | ||||
| @ -360,7 +364,7 @@ | ||||
| 							recording = false; | ||||
| 
 | ||||
| 							await tick(); | ||||
| 							document.getElementById('chat-textarea')?.focus(); | ||||
| 							document.getElementById('chat-input')?.focus(); | ||||
| 
 | ||||
| 							if ($settings?.speechAutoSend ?? false) { | ||||
| 								dispatch('submit', prompt); | ||||
| @ -500,8 +504,195 @@ | ||||
| 									</InputMenu> | ||||
| 								</div> | ||||
| 
 | ||||
| 								<textarea | ||||
| 									id="chat-textarea" | ||||
| 								<div | ||||
| 									bind:this={chatInputContainerElement} | ||||
| 									class="scrollbar-hidden text-left bg-gray-50 dark:bg-gray-850 dark:text-gray-100 outline-none w-full py-3 px-1 rounded-xl resize-none h-[48px] overflow-auto" | ||||
| 								> | ||||
| 									<RichTextInput | ||||
| 										id="chat-input" | ||||
| 										placeholder={placeholder ? placeholder : $i18n.t('Send a Message')} | ||||
| 										bind:value={prompt} | ||||
| 										shiftEnter={!$mobile || | ||||
| 											!( | ||||
| 												'ontouchstart' in window || | ||||
| 												navigator.maxTouchPoints > 0 || | ||||
| 												navigator.msMaxTouchPoints > 0 | ||||
| 											)} | ||||
| 										on:enter={async (e) => { | ||||
| 											if (prompt !== '') { | ||||
| 												dispatch('submit', prompt); | ||||
| 											} | ||||
| 										}} | ||||
| 										on:input={async (e) => { | ||||
| 											if (chatInputContainerElement) { | ||||
| 												chatInputContainerElement.style.height = ''; | ||||
| 												chatInputContainerElement.style.height = | ||||
| 													Math.min(chatInputContainerElement.scrollHeight, 200) + 'px'; | ||||
| 											} | ||||
| 										}} | ||||
| 										on:focus={async (e) => { | ||||
| 											if (chatInputContainerElement) { | ||||
| 												chatInputContainerElement.style.height = ''; | ||||
| 												chatInputContainerElement.style.height = | ||||
| 													Math.min(chatInputContainerElement.scrollHeight, 200) + 'px'; | ||||
| 											} | ||||
| 										}} | ||||
| 										on:keypress={(e) => { | ||||
| 											e = e.detail.event; | ||||
| 											console.log(e); | ||||
| 										}} | ||||
| 										on:keydown={async (e) => { | ||||
| 											e = e.detail.event; | ||||
| 											console.log(e); | ||||
| 
 | ||||
| 											if (chatInputContainerElement) { | ||||
| 												chatInputContainerElement.style.height = ''; | ||||
| 												chatInputContainerElement.style.height = | ||||
| 													Math.min(chatInputContainerElement.scrollHeight, 200) + 'px'; | ||||
| 											} | ||||
| 
 | ||||
| 											const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac | ||||
| 											const commandsContainerElement = | ||||
| 												document.getElementById('commands-container'); | ||||
| 
 | ||||
| 											// 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 && 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 (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, 200) + 'px'; | ||||
| 											} | ||||
| 
 | ||||
| 											if (e.key === 'Escape') { | ||||
| 												console.log('Escape'); | ||||
| 												atSelectedModel = undefined; | ||||
| 											} | ||||
| 										}} | ||||
| 										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); | ||||
| 													} | ||||
| 												} | ||||
| 											} | ||||
| 										}} | ||||
| 									/> | ||||
| 								</div> | ||||
| 
 | ||||
| 								<!-- <textarea | ||||
| 									id="chat-input" | ||||
| 									bind:this={chatTextAreaElement} | ||||
| 									class="scrollbar-hidden bg-gray-50 dark:bg-gray-850 dark:text-gray-100 outline-none w-full py-3 px-1 rounded-xl resize-none h-[48px]" | ||||
| 									placeholder={placeholder ? placeholder : $i18n.t('Send a Message')} | ||||
| @ -526,151 +717,12 @@ | ||||
| 											} | ||||
| 										} | ||||
| 									}} | ||||
| 									on:keydown={async (e) => { | ||||
| 										const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac | ||||
| 										const commandsContainerElement = document.getElementById('commands-container'); | ||||
| 
 | ||||
| 										// 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 && 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 (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, 200) + 'px'; | ||||
| 										} | ||||
| 
 | ||||
| 										if (e.key === 'Escape') { | ||||
| 											console.log('Escape'); | ||||
| 											atSelectedModel = undefined; | ||||
| 										} | ||||
| 									}} | ||||
| 									 | ||||
| 									rows="1" | ||||
| 									on:input={async (e) => { | ||||
| 										e.target.style.height = ''; | ||||
| 										e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px'; | ||||
| 										user = null; | ||||
| 									}} | ||||
| 									on:focus={async (e) => { | ||||
| 										e.target.style.height = ''; | ||||
| 										e.target.style.height = Math.min(e.target.scrollHeight, 200) + '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); | ||||
| 												} | ||||
| 											} | ||||
| 										} | ||||
| 									}} | ||||
| 								/> | ||||
| 									 | ||||
| 									 | ||||
| 									 | ||||
| 								/> --> | ||||
| 
 | ||||
| 								<div class="self-end mb-2 flex space-x-1 mr-1"> | ||||
| 									{#if !history?.currentId || history.messages[history.currentId]?.done == true} | ||||
|  | ||||
| @ -28,14 +28,14 @@ | ||||
| 	$: command = (prompt?.trim() ?? '').split(' ')?.at(-1) ?? ''; | ||||
| </script> | ||||
| 
 | ||||
| {#if ['/', '#', '@'].includes(command?.charAt(0))} | ||||
| {#if ['/', '#', '@'].includes(command?.charAt(0)) || '\\#' === command.slice(0, 2)} | ||||
| 	{#if command?.charAt(0) === '/'} | ||||
| 		<Prompts bind:this={commandElement} bind:prompt bind:files {command} /> | ||||
| 	{:else if command?.charAt(0) === '#'} | ||||
| 	{:else if command?.charAt(0) === '#' || '\\#' === command.slice(0, 2)} | ||||
| 		<Knowledge | ||||
| 			bind:this={commandElement} | ||||
| 			bind:prompt | ||||
| 			{command} | ||||
| 			command={command.includes('\\#') ? command.slice(2) : command} | ||||
| 			on:youtube={(e) => { | ||||
| 				console.log(e); | ||||
| 				dispatch('upload', { | ||||
|  | ||||
| @ -46,7 +46,7 @@ | ||||
| 		dispatch('select', item); | ||||
| 
 | ||||
| 		prompt = removeLastWordFromString(prompt, command); | ||||
| 		const chatInputElement = document.getElementById('chat-textarea'); | ||||
| 		const chatInputElement = document.getElementById('chat-input'); | ||||
| 
 | ||||
| 		await tick(); | ||||
| 		chatInputElement?.focus(); | ||||
| @ -57,7 +57,7 @@ | ||||
| 		dispatch('url', url); | ||||
| 
 | ||||
| 		prompt = removeLastWordFromString(prompt, command); | ||||
| 		const chatInputElement = document.getElementById('chat-textarea'); | ||||
| 		const chatInputElement = document.getElementById('chat-input'); | ||||
| 
 | ||||
| 		await tick(); | ||||
| 		chatInputElement?.focus(); | ||||
| @ -68,7 +68,7 @@ | ||||
| 		dispatch('youtube', url); | ||||
| 
 | ||||
| 		prompt = removeLastWordFromString(prompt, command); | ||||
| 		const chatInputElement = document.getElementById('chat-textarea'); | ||||
| 		const chatInputElement = document.getElementById('chat-input'); | ||||
| 
 | ||||
| 		await tick(); | ||||
| 		chatInputElement?.focus(); | ||||
|  | ||||
| @ -58,7 +58,7 @@ | ||||
| 
 | ||||
| 	onMount(async () => { | ||||
| 		await tick(); | ||||
| 		const chatInputElement = document.getElementById('chat-textarea'); | ||||
| 		const chatInputElement = document.getElementById('chat-input'); | ||||
| 		await tick(); | ||||
| 		chatInputElement?.focus(); | ||||
| 		await tick(); | ||||
|  | ||||
| @ -110,7 +110,7 @@ | ||||
| 
 | ||||
| 		prompt = text; | ||||
| 
 | ||||
| 		const chatInputElement = document.getElementById('chat-textarea'); | ||||
| 		const chatInputElement = document.getElementById('chat-input'); | ||||
| 
 | ||||
| 		await tick(); | ||||
| 
 | ||||
|  | ||||
| @ -213,7 +213,7 @@ | ||||
| 					transcription = `${transcription}${transcript}`; | ||||
| 
 | ||||
| 					await tick(); | ||||
| 					document.getElementById('chat-textarea')?.focus(); | ||||
| 					document.getElementById('chat-input')?.focus(); | ||||
| 
 | ||||
| 					// Restart the inactivity timeout | ||||
| 					timeoutId = setTimeout(() => { | ||||
|  | ||||
| @ -330,7 +330,7 @@ | ||||
| 
 | ||||
| 				await tick(); | ||||
| 
 | ||||
| 				const chatInputElement = document.getElementById('chat-textarea'); | ||||
| 				const chatInputElement = document.getElementById('chat-input'); | ||||
| 				if (chatInputElement) { | ||||
| 					prompt = p; | ||||
| 
 | ||||
|  | ||||
| @ -57,7 +57,7 @@ | ||||
| 		console.log(prompt); | ||||
| 		await tick(); | ||||
| 
 | ||||
| 		const chatInputElement = document.getElementById('chat-textarea'); | ||||
| 		const chatInputElement = document.getElementById('chat-input'); | ||||
| 		if (chatInputElement) { | ||||
| 			chatInputElement.style.height = ''; | ||||
| 			chatInputElement.style.height = Math.min(chatInputElement.scrollHeight, 200) + 'px'; | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| 	import { createEventDispatcher } from 'svelte'; | ||||
| 	const eventDispatch = createEventDispatcher(); | ||||
| 
 | ||||
| 	import { EditorState, Plugin } from 'prosemirror-state'; | ||||
| 	import { EditorState, Plugin, TextSelection } from 'prosemirror-state'; | ||||
| 	import { EditorView, Decoration, DecorationSet } from 'prosemirror-view'; | ||||
| 	import { undo, redo, history } from 'prosemirror-history'; | ||||
| 	import { schema, defaultMarkdownParser, defaultMarkdownSerializer } from 'prosemirror-markdown'; | ||||
| @ -24,6 +24,7 @@ | ||||
| 	export let className = 'input-prose'; | ||||
| 	export let shiftEnter = false; | ||||
| 
 | ||||
| 	export let id = ''; | ||||
| 	export let value = ''; | ||||
| 	export let placeholder = 'Type here...'; | ||||
| 
 | ||||
| @ -189,7 +190,7 @@ | ||||
| 
 | ||||
| 					Enter: (state, dispatch, view) => { | ||||
| 						if (shiftEnter) { | ||||
| 							eventDispatch('submit'); | ||||
| 							eventDispatch('enter'); | ||||
| 							return true; | ||||
| 						} | ||||
| 						return chainCommands( | ||||
| @ -279,10 +280,40 @@ | ||||
| 					return false; | ||||
| 				}, | ||||
| 				paste: (view, event) => { | ||||
| 					eventDispatch('paste', { event }); | ||||
| 					console.log(event); | ||||
| 					if (event.clipboardData) { | ||||
| 						// Check if the pasted content contains image files | ||||
| 						const hasImageFile = Array.from(event.clipboardData.files).some((file) => | ||||
| 							file.type.startsWith('image/') | ||||
| 						); | ||||
| 
 | ||||
| 						// Check for image in dataTransfer items (for cases where files are not available) | ||||
| 						const hasImageItem = Array.from(event.clipboardData.items).some((item) => | ||||
| 							item.type.startsWith('image/') | ||||
| 						); | ||||
| 
 | ||||
| 						console.log('Has image file:', hasImageFile, 'Has image item:', hasImageItem); | ||||
| 
 | ||||
| 						if (hasImageFile) { | ||||
| 							// If there's an image, dispatch the event to the parent | ||||
| 							eventDispatch('paste', { event }); | ||||
| 							event.preventDefault(); | ||||
| 							return true; | ||||
| 						} | ||||
| 
 | ||||
| 						if (hasImageItem) { | ||||
| 							// If there's an image item, dispatch the event to the parent | ||||
| 							eventDispatch('paste', { event }); | ||||
| 							event.preventDefault(); | ||||
| 							return true; | ||||
| 						} | ||||
| 					} | ||||
| 
 | ||||
| 					// For all other cases (text, formatted text, etc.), let ProseMirror handle it | ||||
| 					return false; | ||||
| 				} | ||||
| 			} | ||||
| 			}, | ||||
| 			attributes: { id } | ||||
| 		}); | ||||
| 	}); | ||||
| 
 | ||||
| @ -292,7 +323,8 @@ | ||||
| 		const newState = EditorState.create({ | ||||
| 			doc: newDoc, | ||||
| 			schema, | ||||
| 			plugins: view.state.plugins | ||||
| 			plugins: view.state.plugins, | ||||
| 			selection: TextSelection.atEnd(newDoc) // This sets the cursor at the end | ||||
| 		}); | ||||
| 		view.updateState(newState); | ||||
| 	} | ||||
|  | ||||
| @ -276,7 +276,8 @@ export const removeLastWordFromString = (inputString, wordString) => { | ||||
| 	// Split the string into an array of words
 | ||||
| 	const words = inputString.split(' '); | ||||
| 
 | ||||
| 	if (words.at(-1) === wordString) { | ||||
| 	console.log(words.at(-1), wordString); | ||||
| 	if (words.at(-1) === wordString || (wordString === '' && words.at(-1) === '\\#')) { | ||||
| 		words.pop(); | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -137,7 +137,7 @@ | ||||
| 				if (isShiftPressed && event.key === 'Escape') { | ||||
| 					event.preventDefault(); | ||||
| 					console.log('focusInput'); | ||||
| 					document.getElementById('chat-textarea')?.focus(); | ||||
| 					document.getElementById('chat-input')?.focus(); | ||||
| 				} | ||||
| 
 | ||||
| 				// Check if Ctrl + Shift + ; is pressed | ||||
|  | ||||
| @ -23,7 +23,7 @@ | ||||
| 	fill: #ebbcba; | ||||
| } | ||||
| 
 | ||||
| .rose-pine-dawn #chat-textarea { | ||||
| .rose-pine-dawn #chat-input { | ||||
| 	background: #cecacd; | ||||
| 	margin: 0.3rem; | ||||
| 	padding: 0.5rem; | ||||
|  | ||||
| @ -23,7 +23,7 @@ | ||||
| 	fill: #c4a7e7; | ||||
| } | ||||
| 
 | ||||
| .rose-pine #chat-textarea { | ||||
| .rose-pine #chat-input { | ||||
| 	background: #393552; | ||||
| 	margin: 0.3rem; | ||||
| 	padding: 0.5rem; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user