ContentRenderer.svelte 5.4 KB

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