diff --git a/package-lock.json b/package-lock.json
index 48122812b8..d2fd62c282 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -37,6 +37,7 @@
"@tiptap/extensions": "^3.0.7",
"@tiptap/pm": "^3.0.7",
"@tiptap/starter-kit": "^3.0.7",
+ "@tiptap/suggestion": "^3.4.2",
"@xyflow/svelte": "^0.1.19",
"async": "^3.2.5",
"bits-ui": "^0.21.15",
@@ -3856,18 +3857,17 @@
}
},
"node_modules/@tiptap/suggestion": {
- "version": "3.0.9",
- "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-3.0.9.tgz",
- "integrity": "sha512-irthqfUybezo3IwR6AXvyyTOtkzwfvvst58VXZtTnR1nN6NEcrs3TQoY3bGKGbN83bdiquKh6aU2nLnZfAhoXg==",
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-3.4.2.tgz",
+ "integrity": "sha512-sljtfiDtdAsbPOwrXrFGf64D6sXUjeU3Iz5v3TvN7TVJKozkZ/gaMkPRl+WC1CGwC6BnzQVDBEEa1e+aApV0mA==",
"license": "MIT",
- "peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
- "@tiptap/core": "^3.0.9",
- "@tiptap/pm": "^3.0.9"
+ "@tiptap/core": "^3.4.2",
+ "@tiptap/pm": "^3.4.2"
}
},
"node_modules/@tiptap/y-tiptap": {
diff --git a/package.json b/package.json
index 054baf39c6..19c897cb48 100644
--- a/package.json
+++ b/package.json
@@ -81,6 +81,7 @@
"@tiptap/extensions": "^3.0.7",
"@tiptap/pm": "^3.0.7",
"@tiptap/starter-kit": "^3.0.7",
+ "@tiptap/suggestion": "^3.4.2",
"@xyflow/svelte": "^0.1.19",
"async": "^3.2.5",
"bits-ui": "^0.21.15",
diff --git a/src/lib/components/channel/MessageInput.svelte b/src/lib/components/channel/MessageInput.svelte
index fc85ea2aa1..c7ca93b902 100644
--- a/src/lib/components/channel/MessageInput.svelte
+++ b/src/lib/components/channel/MessageInput.svelte
@@ -753,53 +753,10 @@
e = e.detail.event;
const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
- const commandsContainerElement = document.getElementById('commands-container');
+ const suggestionsContainerElement =
+ document.getElementById('suggestions-container');
- 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 (!suggestionsContainerElement) {
if (
!$mobile ||
!(
diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte
index faf4b30861..fd64c0226a 100644
--- a/src/lib/components/chat/Chat.svelte
+++ b/src/lib/components/chat/Chat.svelte
@@ -2259,7 +2259,6 @@
bind:selectedModels
shareEnabled={!!history.currentId}
{initNewChat}
- showBanners={!showCommands}
archiveChatHandler={() => {}}
{moveChatHandler}
onSaveTempChat={async () => {
diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte
index 28128d5433..d24c5a5d2b 100644
--- a/src/lib/components/chat/MessageInput.svelte
+++ b/src/lib/components/chat/MessageInput.svelte
@@ -76,6 +76,10 @@
import { KokoroWorker } from '$lib/workers/KokoroWorker';
+ import { getSuggestionRenderer } from '../common/RichTextInput/suggestions';
+ import MentionList from '../common/RichTextInput/MentionList.svelte';
+ import CommandSuggestionList from './MessageInput/CommandSuggestionList.svelte';
+
const i18n = getContext('i18n');
export let onChange: Function = () => {};
@@ -428,9 +432,9 @@
};
let command = '';
-
export let showCommands = false;
$: showCommands = ['/', '#', '@'].includes(command?.charAt(0)) || '\\#' === command?.slice(0, 2);
+ let suggestions = null;
let showTools = false;
@@ -845,6 +849,115 @@
};
onMount(async () => {
+ suggestions = [
+ {
+ char: '@',
+ render: getSuggestionRenderer(CommandSuggestionList, {
+ i18n,
+ onSelect: (e) => {
+ const { type, data } = e;
+
+ if (type === 'model') {
+ atSelectedModel = data;
+ }
+
+ document.getElementById('chat-input')?.focus();
+ },
+
+ insertTextHandler: insertTextAtCursor,
+ onUpload: (e) => {
+ const { type, data } = e;
+
+ if (type === 'file') {
+ if (files.find((f) => f.id === data.id)) {
+ return;
+ }
+ files = [
+ ...files,
+ {
+ ...data,
+ status: 'processed'
+ }
+ ];
+ } else {
+ dispatch('upload', e);
+ }
+ }
+ })
+ },
+ {
+ char: '/',
+ render: getSuggestionRenderer(CommandSuggestionList, {
+ i18n,
+ onSelect: (e) => {
+ const { type, data } = e;
+
+ if (type === 'model') {
+ atSelectedModel = data;
+ }
+
+ document.getElementById('chat-input')?.focus();
+ },
+
+ insertTextHandler: insertTextAtCursor,
+ onUpload: (e) => {
+ const { type, data } = e;
+
+ if (type === 'file') {
+ if (files.find((f) => f.id === data.id)) {
+ return;
+ }
+ files = [
+ ...files,
+ {
+ ...data,
+ status: 'processed'
+ }
+ ];
+ } else {
+ dispatch('upload', e);
+ }
+ }
+ })
+ },
+ {
+ char: '#',
+ render: getSuggestionRenderer(CommandSuggestionList, {
+ i18n,
+ onSelect: (e) => {
+ const { type, data } = e;
+
+ if (type === 'model') {
+ atSelectedModel = data;
+ }
+
+ document.getElementById('chat-input')?.focus();
+ },
+
+ insertTextHandler: insertTextAtCursor,
+ onUpload: (e) => {
+ const { type, data } = e;
+
+ if (type === 'file') {
+ if (files.find((f) => f.id === data.id)) {
+ return;
+ }
+ files = [
+ ...files,
+ {
+ ...data,
+ status: 'processed'
+ }
+ ];
+ } else {
+ dispatch('upload', e);
+ }
+ }
+ })
+ }
+ ];
+
+ console.log(suggestions);
loaded = true;
window.setTimeout(() => {
@@ -929,78 +1042,6 @@
{/if}
-
-
- {#if atSelectedModel !== undefined}
-
-
-
-
)
model.id === atSelectedModel.id)?.info?.meta
- ?.profile_image_url ??
- ($i18n.language === 'dg-DG'
- ? `${WEBUI_BASE_URL}/doge.png`
- : `${WEBUI_BASE_URL}/static/favicon.png`)}
- />
-
- {$i18n.t('Talk to model')}:
- {atSelectedModel.name}
-
-
-
-
-
-
-
- {/if}
-
-
{
- const { type, data } = e;
-
- if (type === 'file') {
- if (files.find((f) => f.id === data.id)) {
- return;
- }
- files = [
- ...files,
- {
- ...data,
- status: 'processed'
- }
- ];
- } else {
- dispatch('upload', e);
- }
- }}
- onSelect={(e) => {
- const { type, data } = e;
-
- if (type === 'model') {
- atSelectedModel = data;
- }
-
- document.getElementById('chat-input')?.focus();
- }}
- />
-
@@ -1066,6 +1107,38 @@
class="flex-1 flex flex-col relative w-full shadow-lg rounded-3xl border border-gray-50 dark:border-gray-850 hover:border-gray-100 focus-within:border-gray-100 hover:dark:border-gray-800 focus-within:dark:border-gray-800 transition px-1 bg-white/90 dark:bg-gray-400/5 dark:text-gray-100"
dir={$settings?.chatDirection ?? 'auto'}
>
+ {#if atSelectedModel !== undefined}
+
+
+
+
)
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 files.length > 0}
{#each files as file, fileIdx}
@@ -1075,7 +1148,7 @@
{#if atSelectedModel ? visionCapableModels.length === 0 : selectedModels.length !== visionCapableModels.length}
{
// Remove from UI state
@@ -1161,250 +1235,201 @@
class="scrollbar-hidden rtl:text-right ltr:text-left bg-transparent dark:text-gray-100 outline-hidden w-full pt-2.5 pb-[5px] px-1 resize-none h-fit max-h-80 overflow-auto"
id="chat-input-container"
>
- {#key $settings?.showFormattingToolbar ?? false}
- {
- prompt = e.md;
- command = getCommand();
- }}
- json={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;
- }}
- oncompositionstart={() => (isComposing = true)}
- oncompositionend={(e) => {
- compositionEndedAt = e.timeStamp;
- 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 ||
+ {#if suggestions}
+ {#key $settings?.showFormattingToolbar ?? false}
+ {
+ prompt = e.md;
+ command = getCommand();
+ }}
+ json={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
- )
- ) {
- if (inOrNearComposition(e)) {
- return;
- }
+ ))}
+ 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.'));
+ }
- // 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;
+ const res = await generateAutoCompletion(
+ localStorage.token,
+ selectedModelIds.at(0),
+ text,
+ history?.currentId
+ ? createMessagesList(history, history.currentId)
+ : null
+ ).catch((error) => {
+ console.log(error);
- if (enterPressed) {
- e.preventDefault();
- if (prompt !== '' || files.length > 0) {
- dispatch('submit', prompt);
- }
+ return null;
+ });
+
+ 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 isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
+ const suggestionsContainerElement =
+ document.getElementById('suggestions-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 (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);
-
- 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?.kind === 'file') {
- const file = item.getAsFile();
- if (file) {
- const _files = [file];
- await inputFilesHandler(_files);
- e.preventDefault();
+ if (!suggestionsContainerElement) {
+ if (
+ !$mobile ||
+ !(
+ 'ontouchstart' in window ||
+ navigator.maxTouchPoints > 0 ||
+ navigator.msMaxTouchPoints > 0
+ )
+ ) {
+ if (inOrNearComposition(e)) {
+ return;
}
- } 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'
- }
- );
+ // 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;
- await uploadFileHandler(file, true);
+ if (enterPressed) {
+ e.preventDefault();
+ if (prompt !== '' || files.length > 0) {
+ dispatch('submit', prompt);
}
}
}
}
- }
- }}
- />
- {/key}
+
+ 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);
+
+ 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?.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}
+ {/if}
{:else}