Projects.svelte 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. <script lang="ts">
  2. import { toast } from 'svelte-sonner';
  3. import Fuse from 'fuse.js';
  4. import { createEventDispatcher, tick, getContext, onMount } from 'svelte';
  5. import { removeLastWordFromString, isValidHttpUrl } from '$lib/utils';
  6. import { projects } from '$lib/stores';
  7. const i18n = getContext('i18n');
  8. export let prompt = '';
  9. export let command = '';
  10. const dispatch = createEventDispatcher();
  11. let selectedIdx = 0;
  12. let fuse = null;
  13. let filteredProjects = [];
  14. $: if (fuse) {
  15. filteredProjects = command.slice(1)
  16. ? fuse.search(command).map((e) => {
  17. return e.item;
  18. })
  19. : $projects;
  20. }
  21. $: if (command) {
  22. selectedIdx = 0;
  23. }
  24. type ObjectWithName = {
  25. name: string;
  26. };
  27. const findByName = (obj: ObjectWithName, command: string) => {
  28. const name = obj.name.toLowerCase();
  29. return name.includes(command.toLowerCase().split(' ')?.at(0)?.substring(1) ?? '');
  30. };
  31. export const selectUp = () => {
  32. selectedIdx = Math.max(0, selectedIdx - 1);
  33. };
  34. export const selectDown = () => {
  35. selectedIdx = Math.min(selectedIdx + 1, filteredProjects.length - 1);
  36. };
  37. const confirmSelect = async (item) => {
  38. dispatch('select', item);
  39. prompt = removeLastWordFromString(prompt, command);
  40. const chatInputElement = document.getElementById('chat-textarea');
  41. await tick();
  42. chatInputElement?.focus();
  43. await tick();
  44. };
  45. const confirmSelectWeb = async (url) => {
  46. dispatch('url', url);
  47. prompt = removeLastWordFromString(prompt, command);
  48. const chatInputElement = document.getElementById('chat-textarea');
  49. await tick();
  50. chatInputElement?.focus();
  51. await tick();
  52. };
  53. const confirmSelectYoutube = async (url) => {
  54. dispatch('youtube', url);
  55. prompt = removeLastWordFromString(prompt, command);
  56. const chatInputElement = document.getElementById('chat-textarea');
  57. await tick();
  58. chatInputElement?.focus();
  59. await tick();
  60. };
  61. onMount(() => {
  62. fuse = new Fuse($projects, {
  63. keys: ['name', 'description']
  64. });
  65. });
  66. </script>
  67. {#if filteredProjects.length > 0 || prompt.split(' ')?.at(0)?.substring(1).startsWith('http')}
  68. <div
  69. id="commands-container"
  70. class="pl-1 pr-12 mb-3 text-left w-full absolute bottom-0 left-0 right-0 z-10"
  71. >
  72. <div class="flex w-full dark:border dark:border-gray-850 rounded-lg">
  73. <div class=" bg-gray-50 dark:bg-gray-850 w-10 rounded-l-lg text-center">
  74. <div class=" text-lg font-semibold mt-2">#</div>
  75. </div>
  76. <div
  77. class="max-h-60 flex flex-col w-full rounded-r-xl bg-white dark:bg-gray-900 dark:text-gray-100"
  78. >
  79. <div class="m-1 overflow-y-auto p-1 rounded-r-xl space-y-0.5 scrollbar-hidden">
  80. {#each filteredProjects as project, idx}
  81. <button
  82. class=" px-3 py-1.5 rounded-xl w-full text-left {idx === selectedIdx
  83. ? ' bg-gray-50 dark:bg-gray-850 dark:text-gray-100 selected-command-option-button'
  84. : ''}"
  85. type="button"
  86. on:click={() => {
  87. console.log(project);
  88. confirmSelect(project);
  89. }}
  90. on:mousemove={() => {
  91. selectedIdx = idx;
  92. }}
  93. on:focus={() => {}}
  94. >
  95. <div class=" font-medium text-black dark:text-gray-100 flex items-center gap-1">
  96. <div class="line-clamp-1">
  97. {project.name}
  98. </div>
  99. {#if project?.meta?.legacy}
  100. <div
  101. class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded uppercase text-xs px-1"
  102. >
  103. Legacy Document
  104. </div>
  105. {:else}
  106. <div
  107. class="bg-green-500/20 text-green-700 dark:text-green-200 rounded uppercase text-xs px-1"
  108. >
  109. Project
  110. </div>
  111. {/if}
  112. </div>
  113. <div class=" text-xs text-gray-600 dark:text-gray-100 line-clamp-1">
  114. {project.description}
  115. </div>
  116. </button>
  117. {/each}
  118. {#if prompt
  119. .split(' ')
  120. .some((s) => s.substring(1).startsWith('https://www.youtube.com') || s
  121. .substring(1)
  122. .startsWith('https://youtu.be'))}
  123. <button
  124. class="px-3 py-1.5 rounded-xl w-full text-left bg-gray-50 dark:bg-gray-850 dark:text-gray-100 selected-command-option-button"
  125. type="button"
  126. on:click={() => {
  127. const url = prompt.split(' ')?.at(0)?.substring(1);
  128. if (isValidHttpUrl(url)) {
  129. confirmSelectYoutube(url);
  130. } else {
  131. toast.error(
  132. $i18n.t(
  133. 'Oops! Looks like the URL is invalid. Please double-check and try again.'
  134. )
  135. );
  136. }
  137. }}
  138. >
  139. <div class=" font-medium text-black dark:text-gray-100 line-clamp-1">
  140. {prompt.split(' ')?.at(0)?.substring(1)}
  141. </div>
  142. <div class=" text-xs text-gray-600 line-clamp-1">{$i18n.t('Youtube')}</div>
  143. </button>
  144. {:else if prompt.split(' ')?.at(0)?.substring(1).startsWith('http')}
  145. <button
  146. class="px-3 py-1.5 rounded-xl w-full text-left bg-gray-50 dark:bg-gray-850 dark:text-gray-100 selected-command-option-button"
  147. type="button"
  148. on:click={() => {
  149. const url = prompt.split(' ')?.at(0)?.substring(1);
  150. if (isValidHttpUrl(url)) {
  151. confirmSelectWeb(url);
  152. } else {
  153. toast.error(
  154. $i18n.t(
  155. 'Oops! Looks like the URL is invalid. Please double-check and try again.'
  156. )
  157. );
  158. }
  159. }}
  160. >
  161. <div class=" font-medium text-black dark:text-gray-100 line-clamp-1">
  162. {prompt.split(' ')?.at(0)?.substring(1)}
  163. </div>
  164. <div class=" text-xs text-gray-600 line-clamp-1">{$i18n.t('Web')}</div>
  165. </button>
  166. {/if}
  167. </div>
  168. </div>
  169. </div>
  170. </div>
  171. {/if}