| <script> |
| import { onDestroy, onMount, tick, getContext, createEventDispatcher } from 'svelte'; |
| const i18n = getContext('i18n'); |
| const dispatch = createEventDispatcher(); |
| |
| import Markdown from './Markdown.svelte'; |
| import { chatId, mobile, showArtifacts, showControls, showOverview } from '$lib/stores'; |
| import FloatingButtons from '../ContentRenderer/FloatingButtons.svelte'; |
| import { createMessagesList } from '$lib/utils'; |
| |
| export let id; |
| export let content; |
| export let history; |
| export let model = null; |
| export let sources = null; |
| |
| export let save = false; |
| export let floatingButtons = true; |
| |
| export let onSourceClick = () => {}; |
| export let onAddMessages = () => {}; |
| |
| let contentContainerElement; |
| |
| let floatingButtonsElement; |
| |
| const updateButtonPosition = (event) => { |
| const buttonsContainerElement = document.getElementById(`floating-buttons-${id}`); |
| if ( |
| !contentContainerElement?.contains(event.target) && |
| !buttonsContainerElement?.contains(event.target) |
| ) { |
| closeFloatingButtons(); |
| return; |
| } |
| |
| setTimeout(async () => { |
| await tick(); |
| |
| if (!contentContainerElement?.contains(event.target)) return; |
| |
| let selection = window.getSelection(); |
| |
| if (selection.toString().trim().length > 0) { |
| const range = selection.getRangeAt(0); |
| const rect = range.getBoundingClientRect(); |
| |
| const parentRect = contentContainerElement.getBoundingClientRect(); |
| |
| |
| const top = rect.bottom - parentRect.top; |
| const left = rect.left - parentRect.left; |
| |
| if (buttonsContainerElement) { |
| buttonsContainerElement.style.display = 'block'; |
| |
| |
| const spaceOnRight = parentRect.width - left; |
| let halfScreenWidth = $mobile ? window.innerWidth / 2 : window.innerWidth / 3; |
| |
| if (spaceOnRight < halfScreenWidth) { |
| const right = parentRect.right - rect.right; |
| buttonsContainerElement.style.right = `${right}px`; |
| buttonsContainerElement.style.left = 'auto'; |
| } else { |
| |
| buttonsContainerElement.style.left = `${left}px`; |
| buttonsContainerElement.style.right = 'auto'; |
| } |
| buttonsContainerElement.style.top = `${top + 5}px`; |
| } |
| } else { |
| closeFloatingButtons(); |
| } |
| }, 0); |
| }; |
| |
| const closeFloatingButtons = () => { |
| const buttonsContainerElement = document.getElementById(`floating-buttons-${id}`); |
| if (buttonsContainerElement) { |
| buttonsContainerElement.style.display = 'none'; |
| } |
| |
| if (floatingButtonsElement) { |
| floatingButtonsElement.closeHandler(); |
| } |
| }; |
| |
| const keydownHandler = (e) => { |
| if (e.key === 'Escape') { |
| closeFloatingButtons(); |
| } |
| }; |
| |
| onMount(() => { |
| if (floatingButtons) { |
| contentContainerElement?.addEventListener('mouseup', updateButtonPosition); |
| document.addEventListener('mouseup', updateButtonPosition); |
| document.addEventListener('keydown', keydownHandler); |
| } |
| }); |
| |
| onDestroy(() => { |
| if (floatingButtons) { |
| contentContainerElement?.removeEventListener('mouseup', updateButtonPosition); |
| document.removeEventListener('mouseup', updateButtonPosition); |
| document.removeEventListener('keydown', keydownHandler); |
| } |
| }); |
| </script> |
|
|
| <div bind:this={contentContainerElement}> |
| <Markdown |
| {id} |
| {content} |
| {model} |
| {save} |
| sourceIds={(sources ?? []).reduce((acc, s) => { |
| let ids = []; |
| s.document.forEach((document, index) => { |
| const metadata = s.metadata?.[index]; |
| const id = metadata?.source ?? 'N/A'; |
|
|
| if (metadata?.name) { |
| ids.push(metadata.name); |
| return ids; |
| } |
|
|
| if (id.startsWith('http://') || id.startsWith('https://')) { |
| ids.push(id); |
| } else { |
| ids.push(s?.source?.name ?? id); |
| } |
|
|
| return ids; |
| }); |
|
|
| acc = [...acc, ...ids]; |
|
|
| // remove duplicates |
| return acc.filter((item, index) => acc.indexOf(item) === index); |
| }, [])} |
| {onSourceClick} |
| on:update={(e) => { |
| dispatch('update', e.detail); |
| }} |
| on:code={(e) => { |
| const { lang, code } = e.detail; |
|
|
| if ( |
| (['html', 'svg'].includes(lang) || (lang === 'xml' && code.includes('svg'))) && |
| !$mobile && |
| $chatId |
| ) { |
| showArtifacts.set(true); |
| showControls.set(true); |
| } |
| }} |
| /> |
| </div> |
|
|
| {#if floatingButtons && model} |
| <FloatingButtons |
| bind:this={floatingButtonsElement} |
| {id} |
| model={model?.id} |
| messages={createMessagesList(history, id)} |
| onAdd={({ modelId, parentId, messages }) => { |
| console.log(modelId, parentId, messages); |
| onAddMessages({ modelId, parentId, messages }); |
| closeFloatingButtons(); |
| }} |
| /> |
| {/if} |
|
|