mirror of
https://github.com/open-webui/open-webui
synced 2024-11-24 13:07:25 +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
|
// Select the first model
|
||||||
cy.get('button[aria-label="model-item"]').first().click();
|
cy.get('button[aria-label="model-item"]').first().click();
|
||||||
// Type a message
|
// 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
|
force: true
|
||||||
});
|
});
|
||||||
// Send the message
|
// Send the message
|
||||||
@ -50,7 +50,7 @@ describe('Settings', () => {
|
|||||||
// Select the first model
|
// Select the first model
|
||||||
cy.get('button[aria-label="model-item"]').first().click();
|
cy.get('button[aria-label="model-item"]').first().click();
|
||||||
// Type a message
|
// 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
|
force: true
|
||||||
});
|
});
|
||||||
// Send the message
|
// Send the message
|
||||||
@ -85,7 +85,7 @@ describe('Settings', () => {
|
|||||||
// Select the first model
|
// Select the first model
|
||||||
cy.get('button[aria-label="model-item"]').first().click();
|
cy.get('button[aria-label="model-item"]').first().click();
|
||||||
// Type a message
|
// 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
|
force: true
|
||||||
});
|
});
|
||||||
// Send the message
|
// Send the message
|
||||||
|
@ -125,7 +125,7 @@
|
|||||||
loaded = true;
|
loaded = true;
|
||||||
|
|
||||||
window.setTimeout(() => scrollToBottom(), 0);
|
window.setTimeout(() => scrollToBottom(), 0);
|
||||||
const chatInput = document.getElementById('chat-textarea');
|
const chatInput = document.getElementById('chat-input');
|
||||||
chatInput?.focus();
|
chatInput?.focus();
|
||||||
} else {
|
} else {
|
||||||
await goto('/');
|
await goto('/');
|
||||||
@ -264,7 +264,7 @@
|
|||||||
if (event.data.type === 'input:prompt') {
|
if (event.data.type === 'input:prompt') {
|
||||||
console.debug(event.data.text);
|
console.debug(event.data.text);
|
||||||
|
|
||||||
const inputElement = document.getElementById('chat-textarea');
|
const inputElement = document.getElementById('chat-input');
|
||||||
|
|
||||||
if (inputElement) {
|
if (inputElement) {
|
||||||
prompt = event.data.text;
|
prompt = event.data.text;
|
||||||
@ -327,7 +327,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const chatInput = document.getElementById('chat-textarea');
|
const chatInput = document.getElementById('chat-input');
|
||||||
chatInput?.focus();
|
chatInput?.focus();
|
||||||
|
|
||||||
chats.subscribe(() => {});
|
chats.subscribe(() => {});
|
||||||
@ -501,7 +501,7 @@
|
|||||||
settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
|
settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const chatInput = document.getElementById('chat-textarea');
|
const chatInput = document.getElementById('chat-input');
|
||||||
setTimeout(() => chatInput?.focus(), 0);
|
setTimeout(() => chatInput?.focus(), 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -799,7 +799,7 @@
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Reset chat input textarea
|
// Reset chat input textarea
|
||||||
const chatTextAreaElement = document.getElementById('chat-textarea');
|
const chatTextAreaElement = document.getElementById('chat-input');
|
||||||
|
|
||||||
if (chatTextAreaElement) {
|
if (chatTextAreaElement) {
|
||||||
chatTextAreaElement.value = '';
|
chatTextAreaElement.value = '';
|
||||||
@ -841,6 +841,11 @@
|
|||||||
|
|
||||||
// Wait until history/message have been updated
|
// Wait until history/message have been updated
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
|
// focus on chat input
|
||||||
|
const chatInput = document.getElementById('chat-input');
|
||||||
|
chatInput?.focus();
|
||||||
|
|
||||||
_responses = await sendPrompt(userPrompt, userMessageId, { newChat: true });
|
_responses = await sendPrompt(userPrompt, userMessageId, { newChat: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
import FilesOverlay from './MessageInput/FilesOverlay.svelte';
|
import FilesOverlay from './MessageInput/FilesOverlay.svelte';
|
||||||
import Commands from './MessageInput/Commands.svelte';
|
import Commands from './MessageInput/Commands.svelte';
|
||||||
import XMark from '../icons/XMark.svelte';
|
import XMark from '../icons/XMark.svelte';
|
||||||
|
import RichTextInput from '../common/RichTextInput.svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
@ -53,8 +54,8 @@
|
|||||||
let recording = false;
|
let recording = false;
|
||||||
|
|
||||||
let chatTextAreaElement: HTMLTextAreaElement;
|
let chatTextAreaElement: HTMLTextAreaElement;
|
||||||
|
let chatInputContainerElement;
|
||||||
let filesInputElement;
|
let filesInputElement;
|
||||||
|
|
||||||
let commandsElement;
|
let commandsElement;
|
||||||
|
|
||||||
let inputFiles;
|
let inputFiles;
|
||||||
@ -213,7 +214,10 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
window.setTimeout(() => chatTextAreaElement?.focus(), 0);
|
window.setTimeout(() => {
|
||||||
|
const chatInput = document.getElementById('chat-input');
|
||||||
|
chatInput?.focus();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
|
||||||
@ -351,7 +355,7 @@
|
|||||||
recording = false;
|
recording = false;
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
document.getElementById('chat-textarea')?.focus();
|
document.getElementById('chat-input')?.focus();
|
||||||
}}
|
}}
|
||||||
on:confirm={async (e) => {
|
on:confirm={async (e) => {
|
||||||
const response = e.detail;
|
const response = e.detail;
|
||||||
@ -360,7 +364,7 @@
|
|||||||
recording = false;
|
recording = false;
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
document.getElementById('chat-textarea')?.focus();
|
document.getElementById('chat-input')?.focus();
|
||||||
|
|
||||||
if ($settings?.speechAutoSend ?? false) {
|
if ($settings?.speechAutoSend ?? false) {
|
||||||
dispatch('submit', prompt);
|
dispatch('submit', prompt);
|
||||||
@ -500,8 +504,195 @@
|
|||||||
</InputMenu>
|
</InputMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<textarea
|
<div
|
||||||
id="chat-textarea"
|
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}
|
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]"
|
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')}
|
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"
|
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">
|
<div class="self-end mb-2 flex space-x-1 mr-1">
|
||||||
{#if !history?.currentId || history.messages[history.currentId]?.done == true}
|
{#if !history?.currentId || history.messages[history.currentId]?.done == true}
|
||||||
|
@ -28,14 +28,14 @@
|
|||||||
$: command = (prompt?.trim() ?? '').split(' ')?.at(-1) ?? '';
|
$: command = (prompt?.trim() ?? '').split(' ')?.at(-1) ?? '';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if ['/', '#', '@'].includes(command?.charAt(0))}
|
{#if ['/', '#', '@'].includes(command?.charAt(0)) || '\\#' === command.slice(0, 2)}
|
||||||
{#if command?.charAt(0) === '/'}
|
{#if command?.charAt(0) === '/'}
|
||||||
<Prompts bind:this={commandElement} bind:prompt bind:files {command} />
|
<Prompts bind:this={commandElement} bind:prompt bind:files {command} />
|
||||||
{:else if command?.charAt(0) === '#'}
|
{:else if command?.charAt(0) === '#' || '\\#' === command.slice(0, 2)}
|
||||||
<Knowledge
|
<Knowledge
|
||||||
bind:this={commandElement}
|
bind:this={commandElement}
|
||||||
bind:prompt
|
bind:prompt
|
||||||
{command}
|
command={command.includes('\\#') ? command.slice(2) : command}
|
||||||
on:youtube={(e) => {
|
on:youtube={(e) => {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
dispatch('upload', {
|
dispatch('upload', {
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
dispatch('select', item);
|
dispatch('select', item);
|
||||||
|
|
||||||
prompt = removeLastWordFromString(prompt, command);
|
prompt = removeLastWordFromString(prompt, command);
|
||||||
const chatInputElement = document.getElementById('chat-textarea');
|
const chatInputElement = document.getElementById('chat-input');
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
chatInputElement?.focus();
|
chatInputElement?.focus();
|
||||||
@ -57,7 +57,7 @@
|
|||||||
dispatch('url', url);
|
dispatch('url', url);
|
||||||
|
|
||||||
prompt = removeLastWordFromString(prompt, command);
|
prompt = removeLastWordFromString(prompt, command);
|
||||||
const chatInputElement = document.getElementById('chat-textarea');
|
const chatInputElement = document.getElementById('chat-input');
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
chatInputElement?.focus();
|
chatInputElement?.focus();
|
||||||
@ -68,7 +68,7 @@
|
|||||||
dispatch('youtube', url);
|
dispatch('youtube', url);
|
||||||
|
|
||||||
prompt = removeLastWordFromString(prompt, command);
|
prompt = removeLastWordFromString(prompt, command);
|
||||||
const chatInputElement = document.getElementById('chat-textarea');
|
const chatInputElement = document.getElementById('chat-input');
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
chatInputElement?.focus();
|
chatInputElement?.focus();
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await tick();
|
await tick();
|
||||||
const chatInputElement = document.getElementById('chat-textarea');
|
const chatInputElement = document.getElementById('chat-input');
|
||||||
await tick();
|
await tick();
|
||||||
chatInputElement?.focus();
|
chatInputElement?.focus();
|
||||||
await tick();
|
await tick();
|
||||||
|
@ -110,7 +110,7 @@
|
|||||||
|
|
||||||
prompt = text;
|
prompt = text;
|
||||||
|
|
||||||
const chatInputElement = document.getElementById('chat-textarea');
|
const chatInputElement = document.getElementById('chat-input');
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@
|
|||||||
transcription = `${transcription}${transcript}`;
|
transcription = `${transcription}${transcript}`;
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
document.getElementById('chat-textarea')?.focus();
|
document.getElementById('chat-input')?.focus();
|
||||||
|
|
||||||
// Restart the inactivity timeout
|
// Restart the inactivity timeout
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
|
@ -330,7 +330,7 @@
|
|||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
const chatInputElement = document.getElementById('chat-textarea');
|
const chatInputElement = document.getElementById('chat-input');
|
||||||
if (chatInputElement) {
|
if (chatInputElement) {
|
||||||
prompt = p;
|
prompt = p;
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
console.log(prompt);
|
console.log(prompt);
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
const chatInputElement = document.getElementById('chat-textarea');
|
const chatInputElement = document.getElementById('chat-input');
|
||||||
if (chatInputElement) {
|
if (chatInputElement) {
|
||||||
chatInputElement.style.height = '';
|
chatInputElement.style.height = '';
|
||||||
chatInputElement.style.height = Math.min(chatInputElement.scrollHeight, 200) + 'px';
|
chatInputElement.style.height = Math.min(chatInputElement.scrollHeight, 200) + 'px';
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
const eventDispatch = createEventDispatcher();
|
const eventDispatch = createEventDispatcher();
|
||||||
|
|
||||||
import { EditorState, Plugin } from 'prosemirror-state';
|
import { EditorState, Plugin, TextSelection } from 'prosemirror-state';
|
||||||
import { EditorView, Decoration, DecorationSet } from 'prosemirror-view';
|
import { EditorView, Decoration, DecorationSet } from 'prosemirror-view';
|
||||||
import { undo, redo, history } from 'prosemirror-history';
|
import { undo, redo, history } from 'prosemirror-history';
|
||||||
import { schema, defaultMarkdownParser, defaultMarkdownSerializer } from 'prosemirror-markdown';
|
import { schema, defaultMarkdownParser, defaultMarkdownSerializer } from 'prosemirror-markdown';
|
||||||
@ -24,6 +24,7 @@
|
|||||||
export let className = 'input-prose';
|
export let className = 'input-prose';
|
||||||
export let shiftEnter = false;
|
export let shiftEnter = false;
|
||||||
|
|
||||||
|
export let id = '';
|
||||||
export let value = '';
|
export let value = '';
|
||||||
export let placeholder = 'Type here...';
|
export let placeholder = 'Type here...';
|
||||||
|
|
||||||
@ -189,7 +190,7 @@
|
|||||||
|
|
||||||
Enter: (state, dispatch, view) => {
|
Enter: (state, dispatch, view) => {
|
||||||
if (shiftEnter) {
|
if (shiftEnter) {
|
||||||
eventDispatch('submit');
|
eventDispatch('enter');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return chainCommands(
|
return chainCommands(
|
||||||
@ -279,10 +280,40 @@
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
paste: (view, event) => {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
attributes: { id }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -292,7 +323,8 @@
|
|||||||
const newState = EditorState.create({
|
const newState = EditorState.create({
|
||||||
doc: newDoc,
|
doc: newDoc,
|
||||||
schema,
|
schema,
|
||||||
plugins: view.state.plugins
|
plugins: view.state.plugins,
|
||||||
|
selection: TextSelection.atEnd(newDoc) // This sets the cursor at the end
|
||||||
});
|
});
|
||||||
view.updateState(newState);
|
view.updateState(newState);
|
||||||
}
|
}
|
||||||
|
@ -276,7 +276,8 @@ export const removeLastWordFromString = (inputString, wordString) => {
|
|||||||
// Split the string into an array of words
|
// Split the string into an array of words
|
||||||
const words = inputString.split(' ');
|
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();
|
words.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@
|
|||||||
if (isShiftPressed && event.key === 'Escape') {
|
if (isShiftPressed && event.key === 'Escape') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
console.log('focusInput');
|
console.log('focusInput');
|
||||||
document.getElementById('chat-textarea')?.focus();
|
document.getElementById('chat-input')?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if Ctrl + Shift + ; is pressed
|
// Check if Ctrl + Shift + ; is pressed
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
fill: #ebbcba;
|
fill: #ebbcba;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rose-pine-dawn #chat-textarea {
|
.rose-pine-dawn #chat-input {
|
||||||
background: #cecacd;
|
background: #cecacd;
|
||||||
margin: 0.3rem;
|
margin: 0.3rem;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
fill: #c4a7e7;
|
fill: #c4a7e7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rose-pine #chat-textarea {
|
.rose-pine #chat-input {
|
||||||
background: #393552;
|
background: #393552;
|
||||||
margin: 0.3rem;
|
margin: 0.3rem;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
Loading…
Reference in New Issue
Block a user