ContentRenderer.svelte 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. <script>
  2. import { onDestroy, onMount, tick, getContext, createEventDispatcher } from 'svelte';
  3. const i18n = getContext('i18n');
  4. const dispatch = createEventDispatcher();
  5. import Markdown from './Markdown.svelte';
  6. import { chatId, mobile, showArtifacts, showControls, showOverview } from '$lib/stores';
  7. import FloatingButtons from '../ContentRenderer/FloatingButtons.svelte';
  8. import { createMessagesList } from '$lib/utils';
  9. export let id;
  10. export let content;
  11. export let history;
  12. export let model = null;
  13. export let sources = null;
  14. export let save = false;
  15. export let floatingButtons = true;
  16. export let onSourceClick = () => {};
  17. export let onTaskClick = () => {};
  18. export let onAddMessages = () => {};
  19. let contentContainerElement;
  20. let floatingButtonsElement;
  21. const updateButtonPosition = (event) => {
  22. const buttonsContainerElement = document.getElementById(`floating-buttons-${id}`);
  23. if (
  24. !contentContainerElement?.contains(event.target) &&
  25. !buttonsContainerElement?.contains(event.target)
  26. ) {
  27. closeFloatingButtons();
  28. return;
  29. }
  30. setTimeout(async () => {
  31. await tick();
  32. if (!contentContainerElement?.contains(event.target)) return;
  33. let selection = window.getSelection();
  34. if (selection.toString().trim().length > 0) {
  35. const range = selection.getRangeAt(0);
  36. const rect = range.getBoundingClientRect();
  37. const parentRect = contentContainerElement.getBoundingClientRect();
  38. // Adjust based on parent rect
  39. const top = rect.bottom - parentRect.top;
  40. const left = rect.left - parentRect.left;
  41. if (buttonsContainerElement) {
  42. buttonsContainerElement.style.display = 'block';
  43. // Calculate space available on the right
  44. const spaceOnRight = parentRect.width - left;
  45. let halfScreenWidth = $mobile ? window.innerWidth / 2 : window.innerWidth / 3;
  46. if (spaceOnRight < halfScreenWidth) {
  47. const right = parentRect.right - rect.right;
  48. buttonsContainerElement.style.right = `${right}px`;
  49. buttonsContainerElement.style.left = 'auto'; // Reset left
  50. } else {
  51. // Enough space, position using 'left'
  52. buttonsContainerElement.style.left = `${left}px`;
  53. buttonsContainerElement.style.right = 'auto'; // Reset right
  54. }
  55. buttonsContainerElement.style.top = `${top + 5}px`; // +5 to add some spacing
  56. }
  57. } else {
  58. closeFloatingButtons();
  59. }
  60. }, 0);
  61. };
  62. const closeFloatingButtons = () => {
  63. const buttonsContainerElement = document.getElementById(`floating-buttons-${id}`);
  64. if (buttonsContainerElement) {
  65. buttonsContainerElement.style.display = 'none';
  66. }
  67. if (floatingButtonsElement) {
  68. floatingButtonsElement.closeHandler();
  69. }
  70. };
  71. const keydownHandler = (e) => {
  72. if (e.key === 'Escape') {
  73. closeFloatingButtons();
  74. }
  75. };
  76. onMount(() => {
  77. if (floatingButtons) {
  78. contentContainerElement?.addEventListener('mouseup', updateButtonPosition);
  79. document.addEventListener('mouseup', updateButtonPosition);
  80. document.addEventListener('keydown', keydownHandler);
  81. }
  82. });
  83. onDestroy(() => {
  84. if (floatingButtons) {
  85. contentContainerElement?.removeEventListener('mouseup', updateButtonPosition);
  86. document.removeEventListener('mouseup', updateButtonPosition);
  87. document.removeEventListener('keydown', keydownHandler);
  88. }
  89. });
  90. </script>
  91. <div bind:this={contentContainerElement}>
  92. <Markdown
  93. {id}
  94. {content}
  95. {model}
  96. {save}
  97. sourceIds={(sources ?? []).reduce((acc, s) => {
  98. let ids = [];
  99. s.document.forEach((document, index) => {
  100. const metadata = s.metadata?.[index];
  101. const id = metadata?.source ?? 'N/A';
  102. if (metadata?.name) {
  103. ids.push(metadata.name);
  104. return ids;
  105. }
  106. if (id.startsWith('http://') || id.startsWith('https://')) {
  107. ids.push(id);
  108. } else {
  109. ids.push(s?.source?.name ?? id);
  110. }
  111. return ids;
  112. });
  113. acc = [...acc, ...ids];
  114. // remove duplicates
  115. return acc.filter((item, index) => acc.indexOf(item) === index);
  116. }, [])}
  117. {onSourceClick}
  118. {onTaskClick}
  119. on:update={(e) => {
  120. dispatch('update', e.detail);
  121. }}
  122. on:code={(e) => {
  123. const { lang, code } = e.detail;
  124. if (
  125. (['html', 'svg'].includes(lang) || (lang === 'xml' && code.includes('svg'))) &&
  126. !$mobile &&
  127. $chatId
  128. ) {
  129. showArtifacts.set(true);
  130. showControls.set(true);
  131. }
  132. }}
  133. />
  134. </div>
  135. {#if floatingButtons && model}
  136. <FloatingButtons
  137. bind:this={floatingButtonsElement}
  138. {id}
  139. model={model?.id}
  140. messages={createMessagesList(history, id)}
  141. onAdd={({ modelId, parentId, messages }) => {
  142. console.log(modelId, parentId, messages);
  143. onAddMessages({ modelId, parentId, messages });
  144. closeFloatingButtons();
  145. }}
  146. />
  147. {/if}