ChatControls.svelte 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. <script lang="ts">
  2. import { SvelteFlowProvider } from '@xyflow/svelte';
  3. import { slide } from 'svelte/transition';
  4. import { Pane, PaneResizer } from 'paneforge';
  5. import { onDestroy, onMount, tick } from 'svelte';
  6. import {
  7. mobile,
  8. showControls,
  9. showCallOverlay,
  10. showOverview,
  11. showArtifacts,
  12. showEmbeds
  13. } from '$lib/stores';
  14. import Modal from '../common/Modal.svelte';
  15. import Controls from './Controls/Controls.svelte';
  16. import CallOverlay from './MessageInput/CallOverlay.svelte';
  17. import Drawer from '../common/Drawer.svelte';
  18. import Overview from './Overview.svelte';
  19. import EllipsisVertical from '../icons/EllipsisVertical.svelte';
  20. import Artifacts from './Artifacts.svelte';
  21. import Embeds from './ChatControls/Embeds.svelte';
  22. export let history;
  23. export let models = [];
  24. export let chatId = null;
  25. export let chatFiles = [];
  26. export let params = {};
  27. export let eventTarget: EventTarget;
  28. export let submitPrompt: Function;
  29. export let stopResponse: Function;
  30. export let showMessage: Function;
  31. export let files;
  32. export let modelId;
  33. export let pane;
  34. let mediaQuery;
  35. let largeScreen = false;
  36. let dragged = false;
  37. let minSize = 0;
  38. export const openPane = () => {
  39. if (parseInt(localStorage?.chatControlsSize)) {
  40. const container = document.getElementById('chat-container');
  41. let size = Math.floor(
  42. (parseInt(localStorage?.chatControlsSize) / container.clientWidth) * 100
  43. );
  44. pane.resize(size);
  45. } else {
  46. pane.resize(minSize);
  47. }
  48. };
  49. const handleMediaQuery = async (e) => {
  50. if (e.matches) {
  51. largeScreen = true;
  52. if ($showCallOverlay) {
  53. showCallOverlay.set(false);
  54. await tick();
  55. showCallOverlay.set(true);
  56. }
  57. } else {
  58. largeScreen = false;
  59. if ($showCallOverlay) {
  60. showCallOverlay.set(false);
  61. await tick();
  62. showCallOverlay.set(true);
  63. }
  64. pane = null;
  65. }
  66. };
  67. const onMouseDown = (event) => {
  68. dragged = true;
  69. };
  70. const onMouseUp = (event) => {
  71. dragged = false;
  72. };
  73. onMount(() => {
  74. // listen to resize 1024px
  75. mediaQuery = window.matchMedia('(min-width: 1024px)');
  76. mediaQuery.addEventListener('change', handleMediaQuery);
  77. handleMediaQuery(mediaQuery);
  78. // Select the container element you want to observe
  79. const container = document.getElementById('chat-container');
  80. // initialize the minSize based on the container width
  81. minSize = Math.floor((350 / container.clientWidth) * 100);
  82. // Create a new ResizeObserver instance
  83. const resizeObserver = new ResizeObserver((entries) => {
  84. for (let entry of entries) {
  85. const width = entry.contentRect.width;
  86. // calculate the percentage of 350px
  87. const percentage = (350 / width) * 100;
  88. // set the minSize to the percentage, must be an integer
  89. minSize = Math.floor(percentage);
  90. if ($showControls) {
  91. if (pane && pane.isExpanded() && pane.getSize() < minSize) {
  92. pane.resize(minSize);
  93. } else {
  94. let size = Math.floor(
  95. (parseInt(localStorage?.chatControlsSize) / container.clientWidth) * 100
  96. );
  97. if (size < minSize) {
  98. pane.resize(minSize);
  99. }
  100. }
  101. }
  102. }
  103. });
  104. // Start observing the container's size changes
  105. resizeObserver.observe(container);
  106. document.addEventListener('mousedown', onMouseDown);
  107. document.addEventListener('mouseup', onMouseUp);
  108. });
  109. onDestroy(() => {
  110. showControls.set(false);
  111. mediaQuery.removeEventListener('change', handleMediaQuery);
  112. document.removeEventListener('mousedown', onMouseDown);
  113. document.removeEventListener('mouseup', onMouseUp);
  114. });
  115. const closeHandler = () => {
  116. showControls.set(false);
  117. showOverview.set(false);
  118. showArtifacts.set(false);
  119. showEmbeds.set(false);
  120. if ($showCallOverlay) {
  121. showCallOverlay.set(false);
  122. }
  123. };
  124. $: if (!chatId) {
  125. closeHandler();
  126. }
  127. </script>
  128. <SvelteFlowProvider>
  129. {#if !largeScreen}
  130. {#if $showControls}
  131. <Drawer
  132. show={$showControls}
  133. onClose={() => {
  134. showControls.set(false);
  135. }}
  136. >
  137. <div
  138. class=" {$showCallOverlay || $showOverview || $showArtifacts || $showEmbeds
  139. ? ' h-screen w-full'
  140. : 'px-4 py-3'} h-full"
  141. >
  142. {#if $showCallOverlay}
  143. <div
  144. class=" h-full max-h-[100dvh] bg-white text-gray-700 dark:bg-black dark:text-gray-300 flex justify-center"
  145. >
  146. <CallOverlay
  147. bind:files
  148. {submitPrompt}
  149. {stopResponse}
  150. {modelId}
  151. {chatId}
  152. {eventTarget}
  153. on:close={() => {
  154. showControls.set(false);
  155. }}
  156. />
  157. </div>
  158. {:else if $showEmbeds}
  159. <Embeds />
  160. {:else if $showArtifacts}
  161. <Artifacts {history} />
  162. {:else if $showOverview}
  163. <Overview
  164. {history}
  165. on:nodeclick={(e) => {
  166. showMessage(e.detail.node.data.message);
  167. }}
  168. on:close={() => {
  169. showControls.set(false);
  170. }}
  171. />
  172. {:else}
  173. <Controls
  174. on:close={() => {
  175. showControls.set(false);
  176. }}
  177. {models}
  178. bind:chatFiles
  179. bind:params
  180. />
  181. {/if}
  182. </div>
  183. </Drawer>
  184. {/if}
  185. {:else}
  186. <!-- if $showControls -->
  187. {#if $showControls}
  188. <PaneResizer
  189. class="relative flex items-center justify-center group border-l border-gray-50 dark:border-gray-850 hover:border-gray-200 dark:hover:border-gray-800 transition z-20"
  190. id="controls-resizer"
  191. >
  192. <div
  193. class=" absolute -left-1.5 -right-1.5 -top-0 -bottom-0 z-20 cursor-col-resize bg-transparent"
  194. />
  195. </PaneResizer>
  196. {/if}
  197. <Pane
  198. bind:pane
  199. defaultSize={0}
  200. onResize={(size) => {
  201. if ($showControls && pane.isExpanded()) {
  202. if (size < minSize) {
  203. pane.resize(minSize);
  204. }
  205. if (size < minSize) {
  206. localStorage.chatControlsSize = 0;
  207. } else {
  208. // save the size in pixels to localStorage
  209. const container = document.getElementById('chat-container');
  210. localStorage.chatControlsSize = Math.floor((size / 100) * container.clientWidth);
  211. }
  212. }
  213. }}
  214. onCollapse={() => {
  215. showControls.set(false);
  216. }}
  217. collapsible={true}
  218. class=" z-10 bg-white dark:bg-gray-850"
  219. >
  220. {#if $showControls}
  221. <div class="flex max-h-full min-h-full">
  222. <div
  223. class="w-full {($showOverview || $showArtifacts || $showEmbeds) && !$showCallOverlay
  224. ? ' '
  225. : 'px-4 py-3 bg-white dark:shadow-lg dark:bg-gray-850 '} z-40 pointer-events-auto overflow-y-auto scrollbar-hidden"
  226. id="controls-container"
  227. >
  228. {#if $showCallOverlay}
  229. <div class="w-full h-full flex justify-center">
  230. <CallOverlay
  231. bind:files
  232. {submitPrompt}
  233. {stopResponse}
  234. {modelId}
  235. {chatId}
  236. {eventTarget}
  237. on:close={() => {
  238. showControls.set(false);
  239. }}
  240. />
  241. </div>
  242. {:else if $showEmbeds}
  243. <Embeds overlay={dragged} />
  244. {:else if $showArtifacts}
  245. <Artifacts {history} overlay={dragged} />
  246. {:else if $showOverview}
  247. <Overview
  248. {history}
  249. on:nodeclick={(e) => {
  250. if (e.detail.node.data.message.favorite) {
  251. history.messages[e.detail.node.data.message.id].favorite = true;
  252. } else {
  253. history.messages[e.detail.node.data.message.id].favorite = null;
  254. }
  255. showMessage(e.detail.node.data.message);
  256. }}
  257. on:close={() => {
  258. showControls.set(false);
  259. }}
  260. />
  261. {:else}
  262. <Controls
  263. on:close={() => {
  264. showControls.set(false);
  265. }}
  266. {models}
  267. bind:chatFiles
  268. bind:params
  269. />
  270. {/if}
  271. </div>
  272. </div>
  273. {/if}
  274. </Pane>
  275. {/if}
  276. </SvelteFlowProvider>