|
@@ -3,6 +3,9 @@
|
|
|
import { flyAndScale } from '$lib/utils/transitions';
|
|
|
import { getContext, createEventDispatcher } from 'svelte';
|
|
|
|
|
|
+ import fileSaver from 'file-saver';
|
|
|
+ const { saveAs } = fileSaver;
|
|
|
+
|
|
|
const dispatch = createEventDispatcher();
|
|
|
|
|
|
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
|
@@ -15,8 +18,14 @@
|
|
|
import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte';
|
|
|
import Bookmark from '$lib/components/icons/Bookmark.svelte';
|
|
|
import BookmarkSlash from '$lib/components/icons/BookmarkSlash.svelte';
|
|
|
- import { getChatPinnedStatusById, toggleChatPinnedStatusById } from '$lib/apis/chats';
|
|
|
+ import {
|
|
|
+ getChatById,
|
|
|
+ getChatPinnedStatusById,
|
|
|
+ toggleChatPinnedStatusById
|
|
|
+ } from '$lib/apis/chats';
|
|
|
import { chats } from '$lib/stores';
|
|
|
+ import { createMessagesList } from '$lib/utils';
|
|
|
+ import { downloadChatAsPDF } from '$lib/apis/utils';
|
|
|
|
|
|
const i18n = getContext('i18n');
|
|
|
|
|
@@ -41,6 +50,71 @@
|
|
|
pinned = await getChatPinnedStatusById(localStorage.token, chatId);
|
|
|
};
|
|
|
|
|
|
+ const getChatAsText = async () => {
|
|
|
+ const chat = await getChatById(localStorage.token, chatId);
|
|
|
+ if (!chat) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const history = chat.chat.history;
|
|
|
+ const messages = createMessagesList(history, history.currentId);
|
|
|
+ const chatText = messages.reduce((a, message, i, arr) => {
|
|
|
+ return `${a}### ${message.role.toUpperCase()}\n${message.content}\n\n`;
|
|
|
+ }, '');
|
|
|
+
|
|
|
+ return chatText.trim();
|
|
|
+ };
|
|
|
+
|
|
|
+ const downloadTxt = async () => {
|
|
|
+ const chatText = await getChatAsText();
|
|
|
+
|
|
|
+ let blob = new Blob([chatText], {
|
|
|
+ type: 'text/plain'
|
|
|
+ });
|
|
|
+
|
|
|
+ saveAs(blob, `chat-${chat.chat.title}.txt`);
|
|
|
+ };
|
|
|
+
|
|
|
+ const downloadPdf = async () => {
|
|
|
+ const chat = await getChatById(localStorage.token, chatId);
|
|
|
+ if (!chat) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const history = chat.chat.history;
|
|
|
+ const messages = createMessagesList(history, history.currentId);
|
|
|
+ const blob = await downloadChatAsPDF(chat.chat.title, messages);
|
|
|
+
|
|
|
+ // Create a URL for the blob
|
|
|
+ const url = window.URL.createObjectURL(blob);
|
|
|
+
|
|
|
+ // Create a link element to trigger the download
|
|
|
+ const a = document.createElement('a');
|
|
|
+ a.href = url;
|
|
|
+ a.download = `chat-${chat.chat.title}.pdf`;
|
|
|
+
|
|
|
+ // Append the link to the body and click it programmatically
|
|
|
+ document.body.appendChild(a);
|
|
|
+ a.click();
|
|
|
+
|
|
|
+ // Remove the link from the body
|
|
|
+ document.body.removeChild(a);
|
|
|
+
|
|
|
+ // Revoke the URL to release memory
|
|
|
+ window.URL.revokeObjectURL(url);
|
|
|
+ };
|
|
|
+
|
|
|
+ const downloadJSONExport = async () => {
|
|
|
+ const chat = await getChatById(localStorage.token, chatId);
|
|
|
+
|
|
|
+ if (chat) {
|
|
|
+ let blob = new Blob([JSON.stringify([chat])], {
|
|
|
+ type: 'application/json'
|
|
|
+ });
|
|
|
+ saveAs(blob, `chat-export-${Date.now()}.json`);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
$: if (show) {
|
|
|
checkPinned();
|
|
|
}
|
|
@@ -60,7 +134,7 @@
|
|
|
|
|
|
<div slot="content">
|
|
|
<DropdownMenu.Content
|
|
|
- class="w-full max-w-[160px] rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-xl"
|
|
|
+ class="w-full max-w-[180px] rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-xl"
|
|
|
sideOffset={-2}
|
|
|
side="bottom"
|
|
|
align="start"
|
|
@@ -121,6 +195,59 @@
|
|
|
<div class="flex items-center">{$i18n.t('Share')}</div>
|
|
|
</DropdownMenu.Item>
|
|
|
|
|
|
+ <DropdownMenu.Sub>
|
|
|
+ <DropdownMenu.SubTrigger
|
|
|
+ class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
|
|
+ >
|
|
|
+ <svg
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ fill="none"
|
|
|
+ viewBox="0 0 24 24"
|
|
|
+ stroke-width="1.5"
|
|
|
+ stroke="currentColor"
|
|
|
+ class="size-4"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ stroke-linecap="round"
|
|
|
+ stroke-linejoin="round"
|
|
|
+ d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+
|
|
|
+ <div class="flex items-center">{$i18n.t('Download')}</div>
|
|
|
+ </DropdownMenu.SubTrigger>
|
|
|
+ <DropdownMenu.SubContent
|
|
|
+ class="w-full rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
|
|
|
+ transition={flyAndScale}
|
|
|
+ sideOffset={8}
|
|
|
+ >
|
|
|
+ <DropdownMenu.Item
|
|
|
+ class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
|
|
+ on:click={() => {
|
|
|
+ downloadJSONExport();
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div class="flex items-center line-clamp-1">{$i18n.t('Export chat (.json)')}</div>
|
|
|
+ </DropdownMenu.Item>
|
|
|
+ <DropdownMenu.Item
|
|
|
+ class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
|
|
+ on:click={() => {
|
|
|
+ downloadTxt();
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div class="flex items-center line-clamp-1">{$i18n.t('Plain text (.txt)')}</div>
|
|
|
+ </DropdownMenu.Item>
|
|
|
+
|
|
|
+ <DropdownMenu.Item
|
|
|
+ class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
|
|
+ on:click={() => {
|
|
|
+ downloadPdf();
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div class="flex items-center line-clamp-1">{$i18n.t('PDF document (.pdf)')}</div>
|
|
|
+ </DropdownMenu.Item>
|
|
|
+ </DropdownMenu.SubContent>
|
|
|
+ </DropdownMenu.Sub>
|
|
|
<DropdownMenu.Item
|
|
|
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
|
|
on:click={() => {
|