mirror of
https://github.com/open-webui/open-webui
synced 2025-03-24 14:40:51 +00:00
feat: text select quick actions
This commit is contained in:
parent
1f488a0072
commit
0ad35ffad9
@ -1978,6 +1978,11 @@
|
||||
{mergeResponses}
|
||||
{chatActionHandler}
|
||||
bottomPadding={files.length > 0}
|
||||
on:submit={(e) => {
|
||||
if (e.detail) {
|
||||
submitPrompt(e.detail);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { chats, config, settings, user as _user, mobile, currentChatPage } from '$lib/stores';
|
||||
import { tick, getContext, onMount } from 'svelte';
|
||||
import { tick, getContext, onMount, createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { getChatList, updateChatById } from '$lib/apis/chats';
|
||||
@ -382,6 +383,9 @@
|
||||
{continueResponse}
|
||||
{mergeResponses}
|
||||
{readOnly}
|
||||
on:submit={async (e) => {
|
||||
dispatch('submit', e.detail);
|
||||
}}
|
||||
on:action={async (e) => {
|
||||
if (typeof e.detail === 'string') {
|
||||
await chatActionHandler(chatId, e.detail, message.model, message.id);
|
||||
|
79
src/lib/components/chat/Messages/ContentRenderer.svelte
Normal file
79
src/lib/components/chat/Messages/ContentRenderer.svelte
Normal file
@ -0,0 +1,79 @@
|
||||
<script>
|
||||
import { onDestroy, onMount, tick, createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
import Markdown from './Markdown.svelte';
|
||||
import LightBlub from '$lib/components/icons/LightBlub.svelte';
|
||||
|
||||
export let id;
|
||||
export let content;
|
||||
export let model = null;
|
||||
|
||||
export let floatingButtons = true;
|
||||
|
||||
let contentContainerElement;
|
||||
let buttonsContainerElement;
|
||||
|
||||
const updateButtonPosition = () => {
|
||||
setTimeout(async () => {
|
||||
await tick();
|
||||
let selection = window.getSelection();
|
||||
|
||||
if (selection.toString().trim().length > 0) {
|
||||
const range = selection.getRangeAt(0);
|
||||
const rect = range.getBoundingClientRect();
|
||||
const parentRect = contentContainerElement.getBoundingClientRect();
|
||||
|
||||
// Adjust based on parent rect
|
||||
const top = rect.bottom - parentRect.top;
|
||||
const left = rect.left - parentRect.left;
|
||||
|
||||
buttonsContainerElement.style.display = 'block';
|
||||
buttonsContainerElement.style.left = `${left}px`;
|
||||
buttonsContainerElement.style.top = `${top + 5}px`; // +5 to add some spacing
|
||||
} else {
|
||||
buttonsContainerElement.style.display = 'none';
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
if (floatingButtons) {
|
||||
contentContainerElement?.addEventListener('mouseup', updateButtonPosition);
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (floatingButtons) {
|
||||
contentContainerElement?.removeEventListener('mouseup', updateButtonPosition);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div bind:this={contentContainerElement}>
|
||||
<Markdown {id} {content} {model} />
|
||||
</div>
|
||||
|
||||
{#if floatingButtons}
|
||||
<div
|
||||
bind:this={buttonsContainerElement}
|
||||
class="absolute rounded-lg mt-1 p-1 bg-white dark:bg-gray-850 text-xs text-medium shadow-lg"
|
||||
style="display: none"
|
||||
>
|
||||
<button
|
||||
class="px-1 hover:bg-gray-50 dark:hover:bg-gray-800 rounded flex items-center gap-0.5"
|
||||
on:click={() => {
|
||||
const selection = window.getSelection();
|
||||
dispatch('explain', selection.toString());
|
||||
|
||||
// Clear selection
|
||||
selection.removeAllRanges();
|
||||
buttonsContainerElement.style.display = 'none';
|
||||
}}
|
||||
>
|
||||
<LightBlub className="size-3" />
|
||||
|
||||
<div>Explain</div>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
@ -76,6 +76,9 @@
|
||||
{rateMessage}
|
||||
{continueResponse}
|
||||
{regenerateResponse}
|
||||
on:submit={async (e) => {
|
||||
dispatch('submit', e.detail);
|
||||
}}
|
||||
on:action={async (e) => {
|
||||
dispatch('action', e.detail);
|
||||
}}
|
||||
@ -106,6 +109,9 @@
|
||||
{continueResponse}
|
||||
{regenerateResponse}
|
||||
{mergeResponses}
|
||||
on:submit={async (e) => {
|
||||
dispatch('submit', e.detail);
|
||||
}}
|
||||
on:action={async (e) => {
|
||||
dispatch('action', e.detail);
|
||||
}}
|
||||
|
@ -215,6 +215,9 @@
|
||||
groupedMessageIdsIdx[modelIdx] =
|
||||
groupedMessageIds[modelIdx].messageIds.length - 1;
|
||||
}}
|
||||
on:submit={async (e) => {
|
||||
dispatch('submit', e.detail);
|
||||
}}
|
||||
on:action={async (e) => {
|
||||
dispatch('action', e.detail);
|
||||
}}
|
||||
|
@ -38,6 +38,7 @@
|
||||
|
||||
import type { Writable } from 'svelte/store';
|
||||
import type { i18n as i18nType } from 'i18next';
|
||||
import ContentRenderer from './ContentRenderer.svelte';
|
||||
|
||||
interface MessageType {
|
||||
id: string;
|
||||
@ -468,13 +469,23 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="w-full flex flex-col">
|
||||
<div class="w-full flex flex-col relative" id="response-content-container">
|
||||
{#if message.content === '' && !message.error}
|
||||
<Skeleton />
|
||||
{:else if message.content && message.error !== true}
|
||||
<!-- always show message contents even if there's an error -->
|
||||
<!-- unless message.error === true which is legacy error handling, where the error message is stored in message.content -->
|
||||
<Markdown id={message.id} content={message.content} {model} />
|
||||
<ContentRenderer
|
||||
id={message.id}
|
||||
content={message.content}
|
||||
{model}
|
||||
on:explain={(e) => {
|
||||
dispatch(
|
||||
'submit',
|
||||
`Can you explain this section to me in more detail?\n\n${e.detail}`
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if message.error}
|
||||
|
19
src/lib/components/icons/LightBlub.svelte
Normal file
19
src/lib/components/icons/LightBlub.svelte
Normal file
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'w-4 h-4';
|
||||
export let strokeWidth = '1.5';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={strokeWidth}
|
||||
stroke="currentColor"
|
||||
class={className}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 18v-5.25m0 0a6.01 6.01 0 0 0 1.5-.189m-1.5.189a6.01 6.01 0 0 1-1.5-.189m3.75 7.478a12.06 12.06 0 0 1-4.5 0m3.75 2.383a14.406 14.406 0 0 1-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 1 0-7.517 0c.85.493 1.509 1.333 1.509 2.316V18"
|
||||
/>
|
||||
</svg>
|
Loading…
Reference in New Issue
Block a user