ContentRenderer.svelte 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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, settings, 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. // check if closeHandler is defined
  69. if (typeof floatingButtonsElement?.closeHandler === 'function') {
  70. // call the closeHandler function
  71. floatingButtonsElement?.closeHandler();
  72. }
  73. }
  74. };
  75. const keydownHandler = (e) => {
  76. if (e.key === 'Escape') {
  77. closeFloatingButtons();
  78. }
  79. };
  80. onMount(() => {
  81. if (floatingButtons) {
  82. contentContainerElement?.addEventListener('mouseup', updateButtonPosition);
  83. document.addEventListener('mouseup', updateButtonPosition);
  84. document.addEventListener('keydown', keydownHandler);
  85. }
  86. });
  87. onDestroy(() => {
  88. if (floatingButtons) {
  89. contentContainerElement?.removeEventListener('mouseup', updateButtonPosition);
  90. document.removeEventListener('mouseup', updateButtonPosition);
  91. document.removeEventListener('keydown', keydownHandler);
  92. }
  93. });
  94. </script>
  95. <div bind:this={contentContainerElement}>
  96. <Markdown
  97. {id}
  98. {content}
  99. {model}
  100. {save}
  101. sourceIds={(sources ?? []).reduce((acc, s) => {
  102. let ids = [];
  103. s.document.forEach((document, index) => {
  104. if (model?.info?.meta?.capabilities?.citations == false) {
  105. ids.push('N/A');
  106. return ids;
  107. }
  108. const metadata = s.metadata?.[index];
  109. const id = metadata?.source ?? 'N/A';
  110. if (metadata?.name) {
  111. ids.push(metadata.name);
  112. return ids;
  113. }
  114. if (id.startsWith('http://') || id.startsWith('https://')) {
  115. ids.push(id);
  116. } else {
  117. ids.push(s?.source?.name ?? id);
  118. }
  119. return ids;
  120. });
  121. acc = [...acc, ...ids];
  122. // remove duplicates
  123. return acc.filter((item, index) => acc.indexOf(item) === index);
  124. }, [])}
  125. {onSourceClick}
  126. {onTaskClick}
  127. onUpdate={(value) => {
  128. dispatch('update', value);
  129. }}
  130. onCode={(value) => {
  131. const { lang, code } = value;
  132. if (
  133. ($settings?.detectArtifacts ?? true) &&
  134. (['html', 'svg'].includes(lang) || (lang === 'xml' && code.includes('svg'))) &&
  135. !$mobile &&
  136. $chatId
  137. ) {
  138. showArtifacts.set(true);
  139. showControls.set(true);
  140. }
  141. }}
  142. />
  143. </div>
  144. {#if floatingButtons && model}
  145. <FloatingButtons
  146. bind:this={floatingButtonsElement}
  147. {id}
  148. model={model?.id}
  149. messages={createMessagesList(history, id)}
  150. onAdd={({ modelId, parentId, messages }) => {
  151. console.log(modelId, parentId, messages);
  152. onAddMessages({ modelId, parentId, messages });
  153. closeFloatingButtons();
  154. }}
  155. />
  156. {/if}