open-webui/src/lib/components/chat/Messages/ContentRenderer.svelte

209 lines
5.4 KiB
Svelte
Raw Normal View History

2024-10-05 08:37:39 +00:00
<script>
2024-10-06 23:14:12 +00:00
import { onDestroy, onMount, tick, getContext, createEventDispatcher } from 'svelte';
const i18n = getContext('i18n');
2024-10-05 08:37:39 +00:00
const dispatch = createEventDispatcher();
import Markdown from './Markdown.svelte';
import LightBlub from '$lib/components/icons/LightBlub.svelte';
2024-10-06 23:39:46 +00:00
import { chatId, mobile, showArtifacts, showControls, showOverview } from '$lib/stores';
2024-10-06 23:14:12 +00:00
import ChatBubble from '$lib/components/icons/ChatBubble.svelte';
2024-10-05 08:37:39 +00:00
export let id;
export let content;
export let model = null;
2024-10-05 19:07:45 +00:00
export let save = false;
2024-10-05 08:37:39 +00:00
export let floatingButtons = true;
let contentContainerElement;
let buttonsContainerElement;
2024-10-06 23:14:12 +00:00
let selectedText = '';
let floatingInput = false;
let floatingInputValue = '';
2024-10-06 05:23:06 +00:00
const updateButtonPosition = (event) => {
2024-10-05 08:37:39 +00:00
setTimeout(async () => {
await tick();
2024-10-06 05:23:06 +00:00
// Check if the event target is within the content container
2024-10-06 07:28:33 +00:00
if (!contentContainerElement?.contains(event.target)) return;
2024-10-06 05:23:06 +00:00
2024-10-05 08:37:39 +00:00
let selection = window.getSelection();
if (selection.toString().trim().length > 0) {
2024-10-06 23:14:12 +00:00
floatingInput = false;
2024-10-05 08:37:39 +00:00
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();
2024-10-06 23:14:12 +00:00
// Calculate position relative to the viewport (now that it's in document.body)
const top = rect.bottom + window.scrollY;
const left = rect.left + window.scrollX;
2024-10-05 10:21:22 +00:00
if (buttonsContainerElement) {
buttonsContainerElement.style.display = 'block';
buttonsContainerElement.style.left = `${left}px`;
buttonsContainerElement.style.top = `${top + 5}px`; // +5 to add some spacing
}
2024-10-05 08:37:39 +00:00
} else {
2024-10-05 10:21:22 +00:00
if (buttonsContainerElement) {
buttonsContainerElement.style.display = 'none';
2024-10-06 23:32:44 +00:00
selectedText = '';
2024-10-06 23:14:12 +00:00
floatingInput = false;
2024-10-06 23:32:44 +00:00
floatingInputValue = '';
2024-10-05 10:21:22 +00:00
}
2024-10-05 08:37:39 +00:00
}
}, 0);
};
2024-10-06 23:14:12 +00:00
const selectAskHandler = () => {
dispatch('select', {
type: 'ask',
content: selectedText,
input: floatingInputValue
});
floatingInput = false;
floatingInputValue = '';
selectedText = '';
// Clear selection
window.getSelection().removeAllRanges();
buttonsContainerElement.style.display = 'none';
};
2024-10-05 08:37:39 +00:00
onMount(() => {
if (floatingButtons) {
contentContainerElement?.addEventListener('mouseup', updateButtonPosition);
2024-10-05 10:19:55 +00:00
document.addEventListener('mouseup', updateButtonPosition);
2024-10-05 08:37:39 +00:00
}
});
onDestroy(() => {
if (floatingButtons) {
contentContainerElement?.removeEventListener('mouseup', updateButtonPosition);
2024-10-05 10:19:55 +00:00
document.removeEventListener('mouseup', updateButtonPosition);
2024-10-05 08:37:39 +00:00
}
});
2024-10-06 23:14:12 +00:00
$: if (floatingButtons) {
if (buttonsContainerElement) {
document.body.appendChild(buttonsContainerElement);
}
}
onDestroy(() => {
if (buttonsContainerElement) {
document.body.removeChild(buttonsContainerElement);
}
});
2024-10-05 08:37:39 +00:00
</script>
<div bind:this={contentContainerElement}>
2024-10-05 19:04:36 +00:00
<Markdown
{id}
{content}
{model}
2024-10-05 19:07:45 +00:00
{save}
2024-10-05 19:04:36 +00:00
on:update={(e) => {
dispatch('update', e.detail);
}}
2024-10-06 07:14:05 +00:00
on:code={(e) => {
2024-10-06 19:51:29 +00:00
const { lang, code } = e.detail;
if (
(['html', 'svg'].includes(lang) || (lang === 'xml' && code.includes('svg'))) &&
2024-10-06 23:39:46 +00:00
!$mobile &&
$chatId
2024-10-06 19:51:29 +00:00
) {
2024-10-06 07:14:05 +00:00
showArtifacts.set(true);
showControls.set(true);
}
}}
2024-10-05 19:04:36 +00:00
/>
2024-10-05 08:37:39 +00:00
</div>
{#if floatingButtons}
<div
bind:this={buttonsContainerElement}
2024-10-06 23:14:12 +00:00
class="absolute rounded-lg mt-1 text-xs z-[9999]"
2024-10-05 08:37:39 +00:00
style="display: none"
>
2024-10-06 23:14:12 +00:00
{#if !floatingInput}
<div
2024-10-07 02:09:27 +00:00
class="flex flex-row gap-0.5 shrink-0 p-1 bg-white dark:bg-gray-850 dark:text-gray-100 text-medium rounded-lg shadow-xl"
2024-10-06 23:14:12 +00:00
>
<button
class="px-1 hover:bg-gray-50 dark:hover:bg-gray-800 rounded flex items-center gap-1 min-w-fit"
on:click={() => {
selectedText = window.getSelection().toString();
floatingInput = true;
}}
>
<ChatBubble className="size-3 shrink-0" />
<div class="shrink-0">Ask</div>
</button>
<button
class="px-1 hover:bg-gray-50 dark:hover:bg-gray-800 rounded flex items-center gap-1 min-w-fit"
on:click={() => {
const selection = window.getSelection();
dispatch('select', {
type: 'explain',
content: selection.toString()
});
// Clear selection
selection.removeAllRanges();
buttonsContainerElement.style.display = 'none';
}}
>
<LightBlub className="size-3 shrink-0" />
<div class="shrink-0">Explain</div>
</button>
</div>
{:else}
<div
class="py-1 flex dark:text-gray-100 bg-gray-50 dark:bg-gray-800 border dark:border-gray-800 w-72 rounded-full shadow-xl"
>
<input
type="text"
class="ml-5 bg-transparent outline-none w-full flex-1 text-sm"
placeholder={$i18n.t('Ask a question')}
bind:value={floatingInputValue}
on:keydown={(e) => {
if (e.key === 'Enter') {
selectAskHandler();
}
}}
/>
<div class="ml-1 mr-2">
<button
class="{floatingInputValue !== ''
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
: 'text-white bg-gray-200 dark:text-gray-900 dark:bg-gray-700 disabled'} transition rounded-full p-1.5 m-0.5 self-center"
on:click={() => {
selectAskHandler();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="size-4"
>
<path
fill-rule="evenodd"
d="M8 14a.75.75 0 0 1-.75-.75V4.56L4.03 7.78a.75.75 0 0 1-1.06-1.06l4.5-4.5a.75.75 0 0 1 1.06 0l4.5 4.5a.75.75 0 0 1-1.06 1.06L8.75 4.56v8.69A.75.75 0 0 1 8 14Z"
clip-rule="evenodd"
/>
</svg>
</button>
</div>
</div>
{/if}
2024-10-05 08:37:39 +00:00
</div>
{/if}