| <script lang="ts"> |
| import fileSaver from 'file-saver'; |
| const { saveAs } = fileSaver; |
| |
| import { |
| chats, |
| user, |
| settings, |
| scrollPaginationEnabled, |
| currentChatPage, |
| pinnedChats |
| } from '$lib/stores'; |
| |
| import { |
| archiveAllChats, |
| deleteAllChats, |
| getAllChats, |
| getChatList, |
| getPinnedChatList, |
| importChats |
| } from '$lib/apis/chats'; |
| import { getImportOrigin, convertOpenAIChats } from '$lib/utils'; |
| import { onMount, getContext } from 'svelte'; |
| import { goto } from '$app/navigation'; |
| import { toast } from 'svelte-sonner'; |
| import ArchivedChatsModal from '$lib/components/layout/ArchivedChatsModal.svelte'; |
| import SharedChatsModal from '$lib/components/layout/SharedChatsModal.svelte'; |
| import FilesModal from '$lib/components/layout/FilesModal.svelte'; |
| import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; |
| |
| const i18n = getContext('i18n'); |
| |
| export let saveSettings: Function; |
| |
| |
| let importFiles; |
| |
| let showArchiveConfirmDialog = false; |
| let showDeleteConfirmDialog = false; |
| let showArchivedChatsModal = false; |
| let showSharedChatsModal = false; |
| let showFilesModal = false; |
| |
| let chatImportInputElement: HTMLInputElement; |
| |
| $: if (importFiles) { |
| console.log(importFiles); |
| |
| let reader = new FileReader(); |
| reader.onload = (event) => { |
| let chats = JSON.parse(event.target.result); |
| console.log(chats); |
| if (getImportOrigin(chats) == 'openai') { |
| try { |
| chats = convertOpenAIChats(chats); |
| } catch (error) { |
| console.log('Unable to import chats:', error); |
| } |
| } |
| importChatsHandler(chats); |
| }; |
| |
| if (importFiles.length > 0) { |
| reader.readAsText(importFiles[0]); |
| } |
| } |
| |
| const importChatsHandler = async (_chats) => { |
| const res = await importChats( |
| localStorage.token, |
| _chats.map((chat) => { |
| if (chat.chat) { |
| return { |
| chat: chat.chat, |
| meta: chat.meta ?? {}, |
| pinned: false, |
| folder_id: chat?.folder_id ?? null, |
| created_at: chat?.created_at ?? null, |
| updated_at: chat?.updated_at ?? null |
| }; |
| } else { |
| |
| return { |
| chat: chat, |
| meta: {}, |
| pinned: false, |
| folder_id: null, |
| created_at: chat?.created_at ?? null, |
| updated_at: chat?.updated_at ?? null |
| }; |
| } |
| }) |
| ); |
| if (res) { |
| toast.success(`Successfully imported ${res.length} chats.`); |
| } |
| |
| currentChatPage.set(1); |
| await chats.set(await getChatList(localStorage.token, $currentChatPage)); |
| pinnedChats.set(await getPinnedChatList(localStorage.token)); |
| scrollPaginationEnabled.set(true); |
| }; |
| |
| const exportChats = async () => { |
| let blob = new Blob([JSON.stringify(await getAllChats(localStorage.token))], { |
| type: 'application/json' |
| }); |
| saveAs(blob, `chat-export-${Date.now()}.json`); |
| }; |
| |
| const archiveAllChatsHandler = async () => { |
| await goto('/'); |
| await archiveAllChats(localStorage.token).catch((error) => { |
| toast.error(`${error}`); |
| }); |
| |
| currentChatPage.set(1); |
| await chats.set(await getChatList(localStorage.token, $currentChatPage)); |
| pinnedChats.set([]); |
| scrollPaginationEnabled.set(true); |
| }; |
| |
| const deleteAllChatsHandler = async () => { |
| await goto('/'); |
| await deleteAllChats(localStorage.token).catch((error) => { |
| toast.error(`${error}`); |
| }); |
| |
| currentChatPage.set(1); |
| await chats.set(await getChatList(localStorage.token, $currentChatPage)); |
| scrollPaginationEnabled.set(true); |
| }; |
| |
| const handleArchivedChatsChange = async () => { |
| currentChatPage.set(1); |
| await chats.set(await getChatList(localStorage.token, $currentChatPage)); |
| |
| scrollPaginationEnabled.set(true); |
| }; |
| </script> |
|
|
| <ArchivedChatsModal bind:show={showArchivedChatsModal} onUpdate={handleArchivedChatsChange} /> |
| <SharedChatsModal bind:show={showSharedChatsModal} /> |
| <FilesModal bind:show={showFilesModal} /> |
|
|
| <ConfirmDialog |
| title={$i18n.t('Archive All Chats')} |
| message={$i18n.t('Are you sure you want to archive all chats? This action cannot be undone.')} |
| bind:show={showArchiveConfirmDialog} |
| on:confirm={archiveAllChatsHandler} |
| on:cancel={() => { |
| showArchiveConfirmDialog = false; |
| }} |
| /> |
|
|
| <ConfirmDialog |
| title={$i18n.t('Delete All Chats')} |
| message={$i18n.t('Are you sure you want to delete all chats? This action cannot be undone.')} |
| bind:show={showDeleteConfirmDialog} |
| on:confirm={deleteAllChatsHandler} |
| on:cancel={() => { |
| showDeleteConfirmDialog = false; |
| }} |
| /> |
|
|
| <div id="tab-chats" class="flex flex-col h-full justify-between text-sm"> |
| <div class="space-y-3 overflow-y-scroll max-h-[28rem] md:max-h-full"> |
| <input |
| id="chat-import-input" |
| bind:this={chatImportInputElement} |
| bind:files={importFiles} |
| type="file" |
| accept=".json" |
| hidden |
| /> |
|
|
| <div> |
| <div class="mb-1 text-sm font-medium">{$i18n.t('Chats')}</div> |
|
|
| <div> |
| <div class="py-0.5 flex w-full justify-between"> |
| <div class="self-center text-xs">{$i18n.t('Import Chats')}</div> |
| <button |
| class="p-1 px-3 text-xs flex rounded-sm transition" |
| on:click={() => { |
| chatImportInputElement.click(); |
| }} |
| type="button" |
| > |
| <span class="self-center">{$i18n.t('Import')}</span> |
| </button> |
| </div> |
| </div> |
|
|
| {#if $user?.role === 'admin' || ($user.permissions?.chat?.export ?? true)} |
| <div> |
| <div class="py-0.5 flex w-full justify-between"> |
| <div class="self-center text-xs">{$i18n.t('Export Chats')}</div> |
| <button |
| class="p-1 px-3 text-xs flex rounded-sm transition" |
| on:click={() => { |
| exportChats(); |
| }} |
| type="button" |
| > |
| <span class="self-center">{$i18n.t('Export')}</span> |
| </button> |
| </div> |
| </div> |
| {/if} |
|
|
| <div> |
| <div class="py-0.5 flex w-full justify-between"> |
| <div class="self-center text-xs">{$i18n.t('Archived Chats')}</div> |
| <button |
| class="p-1 px-3 text-xs flex rounded-sm transition" |
| on:click={() => { |
| showArchivedChatsModal = true; |
| }} |
| type="button" |
| > |
| <span class="self-center">{$i18n.t('Manage')}</span> |
| </button> |
| </div> |
| </div> |
|
|
| <div> |
| <div class="py-0.5 flex w-full justify-between"> |
| <div class="self-center text-xs">{$i18n.t('Shared Chats')}</div> |
| <button |
| class="p-1 px-3 text-xs flex rounded-sm transition" |
| on:click={() => { |
| showSharedChatsModal = true; |
| }} |
| type="button" |
| > |
| <span class="self-center">{$i18n.t('Manage')}</span> |
| </button> |
| </div> |
| </div> |
|
|
| <div> |
| <div class="py-0.5 flex w-full justify-between"> |
| <div class="self-center text-xs">{$i18n.t('Archive All Chats')}</div> |
| <button |
| class="p-1 px-3 text-xs flex rounded-sm transition" |
| on:click={() => { |
| showArchiveConfirmDialog = true; |
| }} |
| type="button" |
| > |
| <span class="self-center">{$i18n.t('Archive All')}</span> |
| </button> |
| </div> |
| </div> |
|
|
| <div> |
| <div class="py-0.5 flex w-full justify-between"> |
| <div class="self-center text-xs">{$i18n.t('Delete All Chats')}</div> |
| <button |
| class="p-1 px-3 text-xs flex rounded-sm transition" |
| on:click={() => { |
| showDeleteConfirmDialog = true; |
| }} |
| type="button" |
| > |
| <span class="self-center">{$i18n.t('Delete All')}</span> |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| <div> |
| <div class="mb-1 text-sm font-medium">{$i18n.t('Files')}</div> |
|
|
| <div> |
| <div class="py-0.5 flex w-full justify-between"> |
| <div class="self-center text-xs">{$i18n.t('Manage Files')}</div> |
| <button |
| class="p-1 px-3 text-xs flex rounded-sm transition" |
| on:click={() => { |
| showFilesModal = true; |
| }} |
| type="button" |
| > |
| <span class="self-center">{$i18n.t('Manage')}</span> |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|