ChatControls.svelte 6.7 KB

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