refac: suggestion prompts

This commit is contained in:
Timothy Jaeryang Baek 2025-07-04 23:40:31 +04:00
parent db0d2ae6d4
commit 198b89faa5
7 changed files with 32 additions and 64 deletions

View file

@ -193,6 +193,15 @@
} }
}; };
const onSelect = async (e) => {
const { type, data } = e;
if (type === 'prompt') {
// Handle prompt selection
messageInput?.setText(data);
}
};
$: if (selectedModels && chatIdProp !== '') { $: if (selectedModels && chatIdProp !== '') {
saveSessionSelectedModels(); saveSessionSelectedModels();
} }
@ -2117,6 +2126,7 @@
{chatActionHandler} {chatActionHandler}
{addMessages} {addMessages}
bottomPadding={files.length > 0} bottomPadding={files.length > 0}
{onSelect}
/> />
</div> </div>
</div> </div>
@ -2202,6 +2212,7 @@
toolServers={$toolServers} toolServers={$toolServers}
{stopResponse} {stopResponse}
{createMessagePair} {createMessagePair}
{onSelect}
on:upload={async (e) => { on:upload={async (e) => {
const { type, data } = e.detail; const { type, data } = e.detail;

View file

@ -18,7 +18,7 @@
export let models = []; export let models = [];
export let atSelectedModel; export let atSelectedModel;
export let submitPrompt; export let onSelect = (e) => {};
let mounted = false; let mounted = false;
let selectedModelIdx = 0; let selectedModelIdx = 0;
@ -135,9 +135,7 @@
models[selectedModelIdx]?.info?.meta?.suggestion_prompts ?? models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
$config?.default_prompt_suggestions ?? $config?.default_prompt_suggestions ??
[]} []}
on:select={(e) => { {onSelect}
submitPrompt(e.detail);
}}
/> />
</div> </div>
</div> </div>

View file

@ -119,6 +119,7 @@
if (chatInput) { if (chatInput) {
if ($settings?.richTextInput ?? true) { if ($settings?.richTextInput ?? true) {
chatInputElement.setText(text); chatInputElement.setText(text);
chatInputElement.focus();
} else { } else {
chatInput.value = text; chatInput.value = text;
prompt = text; prompt = text;

View file

@ -51,6 +51,8 @@
export let bottomPadding = false; export let bottomPadding = false;
export let autoScroll; export let autoScroll;
export let onSelect = (e) => {};
let messagesCount = 20; let messagesCount = 20;
let messagesLoading = false; let messagesLoading = false;
@ -395,25 +397,7 @@
<div class={className}> <div class={className}>
{#if Object.keys(history?.messages ?? {}).length == 0} {#if Object.keys(history?.messages ?? {}).length == 0}
<ChatPlaceholder <ChatPlaceholder modelIds={selectedModels} {atSelectedModel} {onSelect} />
modelIds={selectedModels}
{atSelectedModel}
submitPrompt={async (p) => {
let text = p;
if (p.includes('{{CLIPBOARD}}')) {
const clipboardText = await navigator.clipboard.readText().catch((err) => {
toast.error($i18n.t('Failed to read clipboard contents'));
return '{{CLIPBOARD}}';
});
text = p.replaceAll('{{CLIPBOARD}}', clipboardText);
}
prompt = text;
await tick();
}}
/>
{:else} {:else}
<div class="w-full pt-2"> <div class="w-full pt-2">
{#key chatId} {#key chatId}

View file

@ -43,45 +43,12 @@
export let codeInterpreterEnabled = false; export let codeInterpreterEnabled = false;
export let webSearchEnabled = false; export let webSearchEnabled = false;
export let onSelect = (e) => {};
export let toolServers = []; export let toolServers = [];
let models = []; let models = [];
const selectSuggestionPrompt = async (p) => {
let text = p;
if (p.includes('{{CLIPBOARD}}')) {
const clipboardText = await navigator.clipboard.readText().catch((err) => {
toast.error($i18n.t('Failed to read clipboard contents'));
return '{{CLIPBOARD}}';
});
text = p.replaceAll('{{CLIPBOARD}}', clipboardText);
console.log('Clipboard text:', clipboardText, text);
}
prompt = text;
console.log(prompt);
await tick();
const chatInputContainerElement = document.getElementById('chat-input-container');
const chatInputElement = document.getElementById('chat-input');
if (chatInputContainerElement) {
chatInputContainerElement.scrollTop = chatInputContainerElement.scrollHeight;
}
await tick();
if (chatInputElement) {
chatInputElement.focus();
chatInputElement.dispatchEvent(new Event('input'));
}
await tick();
};
let selectedModelIdx = 0; let selectedModelIdx = 0;
$: if (selectedModels.length > 0) { $: if (selectedModels.length > 0) {
@ -255,9 +222,7 @@
$config?.default_prompt_suggestions ?? $config?.default_prompt_suggestions ??
[]} []}
inputValue={prompt} inputValue={prompt}
on:select={(e) => { {onSelect}
selectSuggestionPrompt(e.detail);
}}
/> />
</div> </div>
</div> </div>

View file

@ -1,16 +1,16 @@
<script lang="ts"> <script lang="ts">
import Fuse from 'fuse.js'; import Fuse from 'fuse.js';
import Bolt from '$lib/components/icons/Bolt.svelte'; import Bolt from '$lib/components/icons/Bolt.svelte';
import { onMount, getContext, createEventDispatcher } from 'svelte'; import { onMount, getContext } from 'svelte';
import { settings, WEBUI_NAME } from '$lib/stores'; import { settings, WEBUI_NAME } from '$lib/stores';
import { WEBUI_VERSION } from '$lib/constants'; import { WEBUI_VERSION } from '$lib/constants';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
export let suggestionPrompts = []; export let suggestionPrompts = [];
export let className = ''; export let className = '';
export let inputValue = ''; export let inputValue = '';
export let onSelect = (e) => {};
let sortedPrompts = []; let sortedPrompts = [];
@ -85,13 +85,14 @@
{#if filteredPrompts.length > 0} {#if filteredPrompts.length > 0}
<div role="list" class="max-h-40 overflow-auto scrollbar-none items-start {className}"> <div role="list" class="max-h-40 overflow-auto scrollbar-none items-start {className}">
{#each filteredPrompts as prompt, idx (prompt.id || prompt.content)} {#each filteredPrompts as prompt, idx (prompt.id || prompt.content)}
<!-- svelte-ignore a11y-no-interactive-element-to-noninteractive-role -->
<button <button
role="listitem" role="listitem"
class="waterfall flex flex-col flex-1 shrink-0 w-full justify-between class="waterfall flex flex-col flex-1 shrink-0 w-full justify-between
px-3 py-2 rounded-xl bg-transparent hover:bg-black/5 px-3 py-2 rounded-xl bg-transparent hover:bg-black/5
dark:hover:bg-white/5 transition group" dark:hover:bg-white/5 transition group"
style="animation-delay: {idx * 60}ms" style="animation-delay: {idx * 60}ms"
on:click={() => dispatch('select', prompt.content)} on:click={() => onSelect({ type: 'prompt', data: prompt.content })}
> >
<div class="flex flex-col text-left"> <div class="flex flex-col text-left">
{#if prompt.title && prompt.title[0] !== ''} {#if prompt.title && prompt.title[0] !== ''}

View file

@ -207,6 +207,14 @@
selectNextTemplate(editor.view.state, editor.view.dispatch); selectNextTemplate(editor.view.state, editor.view.dispatch);
}; };
export const focus = () => {
if (editor) {
editor.view.focus();
// Scroll to the top of the editor
editor.view.dispatch(editor.view.state.tr.scrollIntoView());
}
};
// Function to find the next template in the document // Function to find the next template in the document
function findNextTemplate(doc, from = 0) { function findNextTemplate(doc, from = 0) {
const patterns = [{ start: '{{', end: '}}' }]; const patterns = [{ start: '{{', end: '}}' }];