ArchivedChatsModal.svelte 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. <script lang="ts">
  2. import fileSaver from 'file-saver';
  3. const { saveAs } = fileSaver;
  4. import { toast } from 'svelte-sonner';
  5. import dayjs from 'dayjs';
  6. import { getContext, createEventDispatcher } from 'svelte';
  7. const dispatch = createEventDispatcher();
  8. import Modal from '$lib/components/common/Modal.svelte';
  9. import { archiveChatById, deleteChatById, getArchivedChatList } from '$lib/apis/chats';
  10. import Tooltip from '$lib/components/common/Tooltip.svelte';
  11. const i18n = getContext('i18n');
  12. export let show = false;
  13. let searchValue = '';
  14. let chats = [];
  15. const unarchiveChatHandler = async (chatId) => {
  16. const res = await archiveChatById(localStorage.token, chatId).catch((error) => {
  17. toast.error(error);
  18. });
  19. chats = await getArchivedChatList(localStorage.token);
  20. dispatch('change');
  21. };
  22. const deleteChatHandler = async (chatId) => {
  23. const res = await deleteChatById(localStorage.token, chatId).catch((error) => {
  24. toast.error(error);
  25. });
  26. chats = await getArchivedChatList(localStorage.token);
  27. };
  28. const exportChatsHandler = async () => {
  29. let blob = new Blob([JSON.stringify(chats)], {
  30. type: 'application/json'
  31. });
  32. saveAs(blob, `archived-chat-export-${Date.now()}.json`);
  33. };
  34. $: if (show) {
  35. (async () => {
  36. chats = await getArchivedChatList(localStorage.token);
  37. })();
  38. }
  39. </script>
  40. <Modal size="lg" bind:show>
  41. <div>
  42. <div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
  43. <div class=" text-lg font-medium self-center">{$i18n.t('Archived Chats')}</div>
  44. <button
  45. class="self-center"
  46. on:click={() => {
  47. show = false;
  48. }}
  49. >
  50. <svg
  51. xmlns="http://www.w3.org/2000/svg"
  52. viewBox="0 0 20 20"
  53. fill="currentColor"
  54. class="w-5 h-5"
  55. >
  56. <path
  57. d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
  58. />
  59. </svg>
  60. </button>
  61. </div>
  62. <div class="flex flex-col w-full px-5 pb-4 dark:text-gray-200">
  63. <div class=" flex w-full mt-2 space-x-2">
  64. <div class="flex flex-1">
  65. <div class=" self-center ml-1 mr-3">
  66. <svg
  67. xmlns="http://www.w3.org/2000/svg"
  68. viewBox="0 0 20 20"
  69. fill="currentColor"
  70. class="w-4 h-4"
  71. >
  72. <path
  73. fill-rule="evenodd"
  74. d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
  75. clip-rule="evenodd"
  76. />
  77. </svg>
  78. </div>
  79. <input
  80. class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
  81. bind:value={searchValue}
  82. placeholder={$i18n.t('Search Chats')}
  83. />
  84. </div>
  85. </div>
  86. <hr class=" dark:border-gray-850 my-2" />
  87. <div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
  88. {#if chats.length > 0}
  89. <div class="text-left text-sm w-full mb-3 max-h-[22rem] overflow-y-scroll">
  90. <div class="relative overflow-x-auto">
  91. <table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
  92. <thead
  93. class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-800"
  94. >
  95. <tr>
  96. <th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
  97. <th scope="col" class="px-3 py-2 hidden md:flex"> {$i18n.t('Created At')} </th>
  98. <th scope="col" class="px-3 py-2 text-right" />
  99. </tr>
  100. </thead>
  101. <tbody>
  102. {#each chats.filter((c) => searchValue === '' || c.title
  103. .toLowerCase()
  104. .includes(searchValue.toLowerCase())) as chat, idx}
  105. <tr
  106. class="bg-transparent {idx !== chats.length - 1 &&
  107. 'border-b'} dark:bg-gray-900 dark:border-gray-850 text-xs"
  108. >
  109. <td class="px-3 py-1 w-2/3">
  110. <a href="/c/{chat.id}" target="_blank">
  111. <div class=" underline line-clamp-1">
  112. {chat.title}
  113. </div>
  114. </a>
  115. </td>
  116. <td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
  117. <div class="my-auto">
  118. {dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
  119. </div>
  120. </td>
  121. <td class="px-3 py-1 text-right">
  122. <div class="flex justify-end w-full">
  123. <Tooltip content="Unarchive Chat">
  124. <button
  125. class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
  126. on:click={async () => {
  127. unarchiveChatHandler(chat.id);
  128. }}
  129. >
  130. <svg
  131. xmlns="http://www.w3.org/2000/svg"
  132. fill="none"
  133. viewBox="0 0 24 24"
  134. stroke-width="1.5"
  135. stroke="currentColor"
  136. class="size-4"
  137. >
  138. <path
  139. stroke-linecap="round"
  140. stroke-linejoin="round"
  141. d="M9 8.25H7.5a2.25 2.25 0 0 0-2.25 2.25v9a2.25 2.25 0 0 0 2.25 2.25h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25H15m0-3-3-3m0 0-3 3m3-3V15"
  142. />
  143. </svg>
  144. </button>
  145. </Tooltip>
  146. <Tooltip content="Delete Chat">
  147. <button
  148. class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
  149. on:click={async () => {
  150. deleteChatHandler(chat.id);
  151. }}
  152. >
  153. <svg
  154. xmlns="http://www.w3.org/2000/svg"
  155. fill="none"
  156. viewBox="0 0 24 24"
  157. stroke-width="1.5"
  158. stroke="currentColor"
  159. class="w-4 h-4"
  160. >
  161. <path
  162. stroke-linecap="round"
  163. stroke-linejoin="round"
  164. d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
  165. />
  166. </svg>
  167. </button>
  168. </Tooltip>
  169. </div>
  170. </td>
  171. </tr>
  172. {/each}
  173. </tbody>
  174. </table>
  175. </div>
  176. <div class="flex flex-wrap text-sm font-medium gap-1.5 mt-2 m-1">
  177. <button
  178. class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-300 dark:outline-gray-800 rounded-3xl"
  179. on:click={() => {
  180. exportChatsHandler();
  181. }}>Export All Archived Chats</button
  182. >
  183. </div>
  184. </div>
  185. {:else}
  186. <div class="text-left text-sm w-full mb-8">
  187. {$i18n.t('You have no archived conversations.')}
  188. </div>
  189. {/if}
  190. </div>
  191. </div>
  192. </div>
  193. </Modal>