ArchivedChatsModal.svelte 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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. import localizedFormat from 'dayjs/plugin/localizedFormat';
  8. dayjs.extend(localizedFormat);
  9. const dispatch = createEventDispatcher();
  10. import {
  11. archiveChatById,
  12. deleteChatById,
  13. getAllArchivedChats,
  14. getArchivedChatList
  15. } from '$lib/apis/chats';
  16. import Modal from '$lib/components/common/Modal.svelte';
  17. import Tooltip from '$lib/components/common/Tooltip.svelte';
  18. import UnarchiveAllConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
  19. const i18n = getContext('i18n');
  20. export let show = false;
  21. let chats = [];
  22. let searchValue = '';
  23. let showUnarchiveAllConfirmDialog = false;
  24. const unarchiveChatHandler = async (chatId) => {
  25. const res = await archiveChatById(localStorage.token, chatId).catch((error) => {
  26. toast.error(`${error}`);
  27. });
  28. chats = await getArchivedChatList(localStorage.token);
  29. dispatch('change');
  30. };
  31. const deleteChatHandler = async (chatId) => {
  32. const res = await deleteChatById(localStorage.token, chatId).catch((error) => {
  33. toast.error(`${error}`);
  34. });
  35. chats = await getArchivedChatList(localStorage.token);
  36. };
  37. const exportChatsHandler = async () => {
  38. const chats = await getAllArchivedChats(localStorage.token);
  39. let blob = new Blob([JSON.stringify(chats)], {
  40. type: 'application/json'
  41. });
  42. saveAs(blob, `${$i18n.t('archived-chat-export')}-${Date.now()}.json`);
  43. };
  44. const unarchiveAllHandler = async () => {
  45. for (const chat of chats) {
  46. await archiveChatById(localStorage.token, chat.id);
  47. }
  48. chats = await getArchivedChatList(localStorage.token);
  49. };
  50. $: if (show) {
  51. (async () => {
  52. chats = await getArchivedChatList(localStorage.token);
  53. })();
  54. }
  55. </script>
  56. <UnarchiveAllConfirmDialog
  57. bind:show={showUnarchiveAllConfirmDialog}
  58. message={$i18n.t('Are you sure you want to unarchive all archived chats?')}
  59. confirmLabel={$i18n.t('Unarchive All')}
  60. on:confirm={() => {
  61. unarchiveAllHandler();
  62. }}
  63. />
  64. <Modal size="lg" bind:show>
  65. <div>
  66. <div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
  67. <div class=" text-lg font-medium self-center">{$i18n.t('Archived Chats')}</div>
  68. <button
  69. class="self-center"
  70. on:click={() => {
  71. show = false;
  72. }}
  73. >
  74. <svg
  75. xmlns="http://www.w3.org/2000/svg"
  76. viewBox="0 0 20 20"
  77. fill="currentColor"
  78. class="w-5 h-5"
  79. >
  80. <path
  81. fill-rule="evenodd"
  82. 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"
  83. clip-rule="evenodd"
  84. />
  85. </svg>
  86. </button>
  87. </div>
  88. <div class="flex flex-col w-full px-5 pb-4 dark:text-gray-200">
  89. <div class=" flex w-full mt-2 space-x-2">
  90. <div class="flex flex-1">
  91. <div class=" self-center ml-1 mr-3">
  92. <svg
  93. xmlns="http://www.w3.org/2000/svg"
  94. viewBox="0 0 20 20"
  95. fill="currentColor"
  96. class="w-4 h-4"
  97. >
  98. <path
  99. fill-rule="evenodd"
  100. 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"
  101. clip-rule="evenodd"
  102. />
  103. </svg>
  104. </div>
  105. <input
  106. class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-hidden bg-transparent"
  107. bind:value={searchValue}
  108. placeholder={$i18n.t('Search Chats')}
  109. />
  110. </div>
  111. </div>
  112. <hr class="border-gray-100 dark:border-gray-850 my-2" />
  113. <div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
  114. {#if chats.length > 0}
  115. <div class="w-full">
  116. <div class="text-left text-sm w-full mb-3 max-h-[22rem] overflow-y-scroll">
  117. <div class="relative overflow-x-auto">
  118. <table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
  119. <thead
  120. class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-850"
  121. >
  122. <tr>
  123. <th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
  124. <th scope="col" class="px-3 py-2 hidden md:flex">
  125. {$i18n.t('Created At')}
  126. </th>
  127. <th scope="col" class="px-3 py-2 text-right" />
  128. </tr>
  129. </thead>
  130. <tbody>
  131. {#each chats.filter((c) => searchValue === '' || c.title
  132. .toLowerCase()
  133. .includes(searchValue.toLowerCase())) as chat, idx}
  134. <tr
  135. class="bg-transparent {idx !== chats.length - 1 &&
  136. 'border-b'} dark:bg-gray-900 dark:border-gray-850 text-xs"
  137. >
  138. <td class="px-3 py-1 w-2/3">
  139. <a href="/c/{chat.id}" target="_blank">
  140. <div class=" underline line-clamp-1">
  141. {chat.title}
  142. </div>
  143. </a>
  144. </td>
  145. <td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
  146. <div class="my-auto">
  147. {dayjs(chat.created_at * 1000).format('LLL')}
  148. </div>
  149. </td>
  150. <td class="px-3 py-1 text-right">
  151. <div class="flex justify-end w-full">
  152. <Tooltip content={$i18n.t('Unarchive Chat')}>
  153. <button
  154. class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
  155. on:click={async () => {
  156. unarchiveChatHandler(chat.id);
  157. }}
  158. >
  159. <svg
  160. xmlns="http://www.w3.org/2000/svg"
  161. fill="none"
  162. viewBox="0 0 24 24"
  163. stroke-width="1.5"
  164. stroke="currentColor"
  165. class="size-4"
  166. >
  167. <path
  168. stroke-linecap="round"
  169. stroke-linejoin="round"
  170. 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"
  171. />
  172. </svg>
  173. </button>
  174. </Tooltip>
  175. <Tooltip content={$i18n.t('Delete Chat')}>
  176. <button
  177. class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
  178. on:click={async () => {
  179. deleteChatHandler(chat.id);
  180. }}
  181. >
  182. <svg
  183. xmlns="http://www.w3.org/2000/svg"
  184. fill="none"
  185. viewBox="0 0 24 24"
  186. stroke-width="1.5"
  187. stroke="currentColor"
  188. class="w-4 h-4"
  189. >
  190. <path
  191. stroke-linecap="round"
  192. stroke-linejoin="round"
  193. 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"
  194. />
  195. </svg>
  196. </button>
  197. </Tooltip>
  198. </div>
  199. </td>
  200. </tr>
  201. {/each}
  202. </tbody>
  203. </table>
  204. </div>
  205. </div>
  206. <div class="flex flex-wrap text-sm font-medium gap-1.5 mt-2 m-1 justify-end w-full">
  207. <button
  208. 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"
  209. on:click={() => {
  210. showUnarchiveAllConfirmDialog = true;
  211. }}
  212. >
  213. {$i18n.t('Unarchive All Archived Chats')}
  214. </button>
  215. <button
  216. 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"
  217. on:click={() => {
  218. exportChatsHandler();
  219. }}
  220. >
  221. {$i18n.t('Export All Archived Chats')}
  222. </button>
  223. </div>
  224. </div>
  225. {:else}
  226. <div class="text-left text-sm w-full mb-8">
  227. {$i18n.t('You have no archived conversations.')}
  228. </div>
  229. {/if}
  230. </div>
  231. </div>
  232. </div>
  233. </Modal>