|
@@ -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,177 +504,225 @@
|
|
|
</InputMenu>
|
|
|
</div>
|
|
|
|
|
|
- <textarea
|
|
|
- id="chat-textarea"
|
|
|
- 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')}
|
|
|
- bind:value={prompt}
|
|
|
- on:keypress={(e) => {
|
|
|
- if (
|
|
|
- !$mobile ||
|
|
|
+ <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
|
|
|
- )
|
|
|
- ) {
|
|
|
- // Prevent Enter key from creating a new line
|
|
|
- if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
+ )}
|
|
|
+ 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);
|
|
|
}
|
|
|
|
|
|
- // Submit the prompt when Enter key is pressed
|
|
|
- if (prompt !== '' && e.key === 'Enter' && !e.shiftKey) {
|
|
|
- dispatch('submit', 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();
|
|
|
}
|
|
|
- }
|
|
|
- }}
|
|
|
- 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');
|
|
|
+ if (prompt === '' && e.key == 'ArrowUp') {
|
|
|
+ e.preventDefault();
|
|
|
|
|
|
- const regenerateButton = [
|
|
|
- ...document.getElementsByClassName('regenerate-response-button')
|
|
|
- ]?.at(-1);
|
|
|
+ const userMessageElement = [
|
|
|
+ ...document.getElementsByClassName('user-message')
|
|
|
+ ]?.at(-1);
|
|
|
|
|
|
- regenerateButton?.click();
|
|
|
- }
|
|
|
+ const editButton = [
|
|
|
+ ...document.getElementsByClassName('edit-user-message-button')
|
|
|
+ ]?.at(-1);
|
|
|
|
|
|
- if (prompt === '' && e.key == 'ArrowUp') {
|
|
|
- e.preventDefault();
|
|
|
+ console.log(userMessageElement);
|
|
|
|
|
|
- const userMessageElement = [
|
|
|
- ...document.getElementsByClassName('user-message')
|
|
|
- ]?.at(-1);
|
|
|
+ userMessageElement.scrollIntoView({ block: 'center' });
|
|
|
+ editButton?.click();
|
|
|
+ }
|
|
|
|
|
|
- const editButton = [
|
|
|
- ...document.getElementsByClassName('edit-user-message-button')
|
|
|
- ]?.at(-1);
|
|
|
+ if (commandsContainerElement && e.key === 'ArrowUp') {
|
|
|
+ e.preventDefault();
|
|
|
+ commandsElement.selectUp();
|
|
|
|
|
|
- console.log(userMessageElement);
|
|
|
+ const commandOptionButton = [
|
|
|
+ ...document.getElementsByClassName('selected-command-option-button')
|
|
|
+ ]?.at(-1);
|
|
|
+ commandOptionButton.scrollIntoView({ block: 'center' });
|
|
|
+ }
|
|
|
|
|
|
- userMessageElement.scrollIntoView({ block: 'center' });
|
|
|
- editButton?.click();
|
|
|
- }
|
|
|
+ if (commandsContainerElement && e.key === 'ArrowDown') {
|
|
|
+ e.preventDefault();
|
|
|
+ commandsElement.selectDown();
|
|
|
|
|
|
- if (commandsContainerElement && e.key === 'ArrowUp') {
|
|
|
- e.preventDefault();
|
|
|
- commandsElement.selectUp();
|
|
|
+ const commandOptionButton = [
|
|
|
+ ...document.getElementsByClassName('selected-command-option-button')
|
|
|
+ ]?.at(-1);
|
|
|
+ commandOptionButton.scrollIntoView({ block: 'center' });
|
|
|
+ }
|
|
|
|
|
|
- const commandOptionButton = [
|
|
|
- ...document.getElementsByClassName('selected-command-option-button')
|
|
|
- ]?.at(-1);
|
|
|
- commandOptionButton.scrollIntoView({ block: 'center' });
|
|
|
- }
|
|
|
+ if (commandsContainerElement && e.key === 'Enter') {
|
|
|
+ e.preventDefault();
|
|
|
|
|
|
- if (commandsContainerElement && e.key === 'ArrowDown') {
|
|
|
- e.preventDefault();
|
|
|
- commandsElement.selectDown();
|
|
|
+ const commandOptionButton = [
|
|
|
+ ...document.getElementsByClassName('selected-command-option-button')
|
|
|
+ ]?.at(-1);
|
|
|
|
|
|
- const commandOptionButton = [
|
|
|
- ...document.getElementsByClassName('selected-command-option-button')
|
|
|
- ]?.at(-1);
|
|
|
- commandOptionButton.scrollIntoView({ block: 'center' });
|
|
|
- }
|
|
|
+ if (e.shiftKey) {
|
|
|
+ prompt = `${prompt}\n`;
|
|
|
+ } else if (commandOptionButton) {
|
|
|
+ commandOptionButton?.click();
|
|
|
+ } else {
|
|
|
+ document.getElementById('send-message-button')?.click();
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (commandsContainerElement && e.key === 'Enter') {
|
|
|
- e.preventDefault();
|
|
|
+ if (commandsContainerElement && e.key === 'Tab') {
|
|
|
+ e.preventDefault();
|
|
|
|
|
|
- const commandOptionButton = [
|
|
|
- ...document.getElementsByClassName('selected-command-option-button')
|
|
|
- ]?.at(-1);
|
|
|
+ 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();
|
|
|
- }
|
|
|
- }
|
|
|
+ } else if (e.key === 'Tab') {
|
|
|
+ const words = findWordIndices(prompt);
|
|
|
|
|
|
- if (commandsContainerElement && e.key === 'Tab') {
|
|
|
- e.preventDefault();
|
|
|
+ if (words.length > 0) {
|
|
|
+ const word = words.at(0);
|
|
|
+ const fullPrompt = prompt;
|
|
|
|
|
|
- const commandOptionButton = [
|
|
|
- ...document.getElementsByClassName('selected-command-option-button')
|
|
|
- ]?.at(-1);
|
|
|
+ prompt = prompt.substring(0, word?.endIndex + 1);
|
|
|
+ await tick();
|
|
|
|
|
|
- commandOptionButton?.click();
|
|
|
- } else if (e.key === 'Tab') {
|
|
|
- const words = findWordIndices(prompt);
|
|
|
+ e.target.scrollTop = e.target.scrollHeight;
|
|
|
+ prompt = fullPrompt;
|
|
|
+ await tick();
|
|
|
|
|
|
- if (words.length > 0) {
|
|
|
- const word = words.at(0);
|
|
|
- const fullPrompt = prompt;
|
|
|
+ e.preventDefault();
|
|
|
+ e.target.setSelectionRange(word?.startIndex, word.endIndex + 1);
|
|
|
+ }
|
|
|
|
|
|
- prompt = prompt.substring(0, word?.endIndex + 1);
|
|
|
- await tick();
|
|
|
+ e.target.style.height = '';
|
|
|
+ e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
|
|
|
+ }
|
|
|
|
|
|
- e.target.scrollTop = e.target.scrollHeight;
|
|
|
- prompt = fullPrompt;
|
|
|
- await tick();
|
|
|
+ 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')}
|
|
|
+ bind:value={prompt}
|
|
|
+ on:keypress={(e) => {
|
|
|
+ if (
|
|
|
+ !$mobile ||
|
|
|
+ !(
|
|
|
+ 'ontouchstart' in window ||
|
|
|
+ navigator.maxTouchPoints > 0 ||
|
|
|
+ navigator.msMaxTouchPoints > 0
|
|
|
+ )
|
|
|
+ ) {
|
|
|
+ // Prevent Enter key from creating a new line
|
|
|
+ if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
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);
|
|
|
- }
|
|
|
+ // Submit the prompt when Enter key is pressed
|
|
|
+ if (prompt !== '' && e.key === 'Enter' && !e.shiftKey) {
|
|
|
+ dispatch('submit', prompt);
|
|
|
}
|
|
|
}
|
|
|
}}
|
|
|
- />
|
|
|
+
|
|
|
+ rows="1"
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /> -->
|
|
|
|
|
|
<div class="self-end mb-2 flex space-x-1 mr-1">
|
|
|
{#if !history?.currentId || history.messages[history.currentId]?.done == true}
|