forked from open-webui/open-webui
		
	feat: chat pdf download
This commit is contained in:
		
							parent
							
								
									944efd2cd8
								
							
						
					
					
						commit
						000bea84ae
					
				
					 5 changed files with 104 additions and 97 deletions
				
			
		| 
						 | 
				
			
			@ -2,21 +2,13 @@
 | 
			
		|||
	import { getContext } from 'svelte';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
 | 
			
		||||
	import { Separator } from 'bits-ui';
 | 
			
		||||
	import { getChatById, shareChatById } from '$lib/apis/chats';
 | 
			
		||||
	import { WEBUI_NAME, chatId, modelfiles, settings, showSettings } from '$lib/stores';
 | 
			
		||||
 | 
			
		||||
	import { slide } from 'svelte/transition';
 | 
			
		||||
	import ShareChatModal from '../chat/ShareChatModal.svelte';
 | 
			
		||||
	import TagInput from '../common/Tags/TagInput.svelte';
 | 
			
		||||
	import ModelSelector from '../chat/ModelSelector.svelte';
 | 
			
		||||
	import Tooltip from '../common/Tooltip.svelte';
 | 
			
		||||
 | 
			
		||||
	import EllipsisVertical from '../icons/EllipsisVertical.svelte';
 | 
			
		||||
	import ChevronDown from '../icons/ChevronDown.svelte';
 | 
			
		||||
	import ChevronUpDown from '../icons/ChevronUpDown.svelte';
 | 
			
		||||
	import Menu from './Navbar/Menu.svelte';
 | 
			
		||||
	import TagChatModal from '../chat/TagChatModal.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -24,6 +16,7 @@
 | 
			
		|||
	export let title: string = $WEBUI_NAME;
 | 
			
		||||
	export let shareEnabled: boolean = false;
 | 
			
		||||
 | 
			
		||||
	export let chat;
 | 
			
		||||
	export let selectedModels;
 | 
			
		||||
 | 
			
		||||
	export let tags = [];
 | 
			
		||||
| 
						 | 
				
			
			@ -33,63 +26,15 @@
 | 
			
		|||
	export let showModelSelector = true;
 | 
			
		||||
 | 
			
		||||
	let showShareChatModal = false;
 | 
			
		||||
	let showTagChatModal = false;
 | 
			
		||||
	let showDownloadChatModal = false;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<ShareChatModal bind:show={showShareChatModal} />
 | 
			
		||||
<!-- <TagChatModal bind:show={showTagChatModal} {tags} {deleteTag} {addTag} /> -->
 | 
			
		||||
<nav id="nav" class=" sticky py-2.5 top-0 flex flex-row justify-center z-30">
 | 
			
		||||
	<div
 | 
			
		||||
		class=" flex {$settings?.fullScreenMode ?? null ? 'max-w-full' : 'max-w-3xl'} 
 | 
			
		||||
		 w-full mx-auto px-3"
 | 
			
		||||
	>
 | 
			
		||||
		<!-- {#if shareEnabled}
 | 
			
		||||
			<div class="flex items-center w-full max-w-full">
 | 
			
		||||
				<div class=" flex-1 self-center font-medium line-clamp-1">
 | 
			
		||||
					<div>
 | 
			
		||||
						{title != '' ? title : $WEBUI_NAME}
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="pl-2 self-center flex items-center">
 | 
			
		||||
					<div class=" mr-1">
 | 
			
		||||
						<Tags {tags} {deleteTag} {addTag} />
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<Tooltip content="Share">
 | 
			
		||||
						<button
 | 
			
		||||
							class="cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
 | 
			
		||||
							on:click={async () => {
 | 
			
		||||
								showShareChatModal = !showShareChatModal;
 | 
			
		||||
 | 
			
		||||
								// console.log(showShareChatModal);
 | 
			
		||||
							}}
 | 
			
		||||
						>
 | 
			
		||||
							<div class=" m-auto self-center">
 | 
			
		||||
								<svg
 | 
			
		||||
									xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
									viewBox="0 0 24 24"
 | 
			
		||||
									fill="currentColor"
 | 
			
		||||
									class="w-4 h-4"
 | 
			
		||||
								>
 | 
			
		||||
									<path
 | 
			
		||||
										fill-rule="evenodd"
 | 
			
		||||
										d="M15.75 4.5a3 3 0 1 1 .825 2.066l-8.421 4.679a3.002 3.002 0 0 1 0 1.51l8.421 4.679a3 3 0 1 1-.729 1.31l-8.421-4.678a3 3 0 1 1 0-4.132l8.421-4.679a3 3 0 0 1-.096-.755Z"
 | 
			
		||||
										clip-rule="evenodd"
 | 
			
		||||
									/>
 | 
			
		||||
								</svg>
 | 
			
		||||
							</div>
 | 
			
		||||
						</button>
 | 
			
		||||
					</Tooltip>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if} -->
 | 
			
		||||
 | 
			
		||||
		<!-- <div class=" flex-1 self-center font-medium line-clamp-1">
 | 
			
		||||
			<div>
 | 
			
		||||
				{title != '' ? title : $WEBUI_NAME}
 | 
			
		||||
			</div>
 | 
			
		||||
		</div> -->
 | 
			
		||||
 | 
			
		||||
		<div class="flex items-center w-full max-w-full">
 | 
			
		||||
			<div class="flex-1 overflow-hidden max-w-full">
 | 
			
		||||
				{#if showModelSelector}
 | 
			
		||||
| 
						 | 
				
			
			@ -132,10 +77,14 @@
 | 
			
		|||
					</Tooltip>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<Menu
 | 
			
		||||
						{chat}
 | 
			
		||||
						{shareEnabled}
 | 
			
		||||
						shareHandler={() => {
 | 
			
		||||
							showShareChatModal = !showShareChatModal;
 | 
			
		||||
						}}
 | 
			
		||||
						downloadHandler={() => {
 | 
			
		||||
							showDownloadChatModal = !showDownloadChatModal;
 | 
			
		||||
						}}
 | 
			
		||||
						{tags}
 | 
			
		||||
						{deleteTag}
 | 
			
		||||
						{addTag}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,23 +1,58 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { DropdownMenu } from 'bits-ui';
 | 
			
		||||
 | 
			
		||||
	import fileSaver from 'file-saver';
 | 
			
		||||
	const { saveAs } = fileSaver;
 | 
			
		||||
 | 
			
		||||
	import { jsPDF } from 'jspdf';
 | 
			
		||||
 | 
			
		||||
	import { showSettings } from '$lib/stores';
 | 
			
		||||
	import { flyAndScale } from '$lib/utils/transitions';
 | 
			
		||||
 | 
			
		||||
	import Dropdown from '$lib/components/common/Dropdown.svelte';
 | 
			
		||||
	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
 | 
			
		||||
	import Pencil from '$lib/components/icons/Pencil.svelte';
 | 
			
		||||
	import Tooltip from '$lib/components/common/Tooltip.svelte';
 | 
			
		||||
	import { showSettings } from '$lib/stores';
 | 
			
		||||
	import Tags from '$lib/components/common/Tags.svelte';
 | 
			
		||||
 | 
			
		||||
	export let shareEnabled: boolean = false;
 | 
			
		||||
	export let shareHandler: Function;
 | 
			
		||||
	export let downloadHandler: Function;
 | 
			
		||||
 | 
			
		||||
	// export let tagHandler: Function;
 | 
			
		||||
 | 
			
		||||
	export let chat;
 | 
			
		||||
	export let tags;
 | 
			
		||||
	export let deleteTag: Function;
 | 
			
		||||
	export let addTag: Function;
 | 
			
		||||
 | 
			
		||||
	export let onClose: Function = () => {};
 | 
			
		||||
 | 
			
		||||
	const downloadChatAsTxt = async () => {
 | 
			
		||||
		const _chat = chat.chat;
 | 
			
		||||
		console.log('download', chat);
 | 
			
		||||
 | 
			
		||||
		const chatText = _chat.messages.reduce((a, message, i, arr) => {
 | 
			
		||||
			return `${a}### ${message.role.toUpperCase()}\n${message.content}\n\n`;
 | 
			
		||||
		}, '');
 | 
			
		||||
 | 
			
		||||
		let blob = new Blob([chatText], {
 | 
			
		||||
			type: 'text/plain'
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		saveAs(blob, `chat-${_chat.title}.txt`);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const downloadChatAsPdf = async () => {
 | 
			
		||||
		const _chat = chat.chat;
 | 
			
		||||
		console.log('download', chat);
 | 
			
		||||
 | 
			
		||||
		const doc = new jsPDF();
 | 
			
		||||
 | 
			
		||||
		const chatText = _chat.messages.reduce((a, message, i, arr) => {
 | 
			
		||||
			return `${a}### ${message.role.toUpperCase()}\n${message.content}\n\n`;
 | 
			
		||||
		}, '');
 | 
			
		||||
 | 
			
		||||
		doc.text(chatText, 10, 10);
 | 
			
		||||
		doc.save(`chat-${_chat.title}.pdf`);
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<Dropdown
 | 
			
		||||
| 
						 | 
				
			
			@ -31,14 +66,14 @@
 | 
			
		|||
 | 
			
		||||
	<div slot="content">
 | 
			
		||||
		<DropdownMenu.Content
 | 
			
		||||
			class="w-full max-w-[150px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
 | 
			
		||||
			class="w-full max-w-[200px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-900 dark:text-white shadow-lg"
 | 
			
		||||
			sideOffset={8}
 | 
			
		||||
			side="bottom"
 | 
			
		||||
			align="end"
 | 
			
		||||
			transition={flyAndScale}
 | 
			
		||||
		>
 | 
			
		||||
			<DropdownMenu.Item
 | 
			
		||||
				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer"
 | 
			
		||||
				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-850 rounded-md"
 | 
			
		||||
				on:click={async () => {
 | 
			
		||||
					await showSettings.set(!$showSettings);
 | 
			
		||||
				}}
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +84,7 @@
 | 
			
		|||
					viewBox="0 0 24 24"
 | 
			
		||||
					stroke-width="1.5"
 | 
			
		||||
					stroke="currentColor"
 | 
			
		||||
					class="w-5 h-5"
 | 
			
		||||
					class="size-4"
 | 
			
		||||
				>
 | 
			
		||||
					<path
 | 
			
		||||
						stroke-linecap="round"
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +102,7 @@
 | 
			
		|||
 | 
			
		||||
			{#if shareEnabled}
 | 
			
		||||
				<DropdownMenu.Item
 | 
			
		||||
					class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer"
 | 
			
		||||
					class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-850 rounded-md"
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						shareHandler();
 | 
			
		||||
					}}
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +122,59 @@
 | 
			
		|||
					<div class="flex items-center">Share</div>
 | 
			
		||||
				</DropdownMenu.Item>
 | 
			
		||||
 | 
			
		||||
				<hr class="border-gray-100 dark:border-gray-800 my-1" />
 | 
			
		||||
				<!-- <DropdownMenu.Item
 | 
			
		||||
					class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer"
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						downloadHandler();
 | 
			
		||||
					}}
 | 
			
		||||
				/> -->
 | 
			
		||||
				<DropdownMenu.Sub>
 | 
			
		||||
					<DropdownMenu.SubTrigger
 | 
			
		||||
						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-850 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">Download</div>
 | 
			
		||||
					</DropdownMenu.SubTrigger>
 | 
			
		||||
					<DropdownMenu.SubContent
 | 
			
		||||
						class="w-full rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-900 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 dark:hover:bg-gray-850 rounded-md"
 | 
			
		||||
							on:click={() => {
 | 
			
		||||
								downloadChatAsTxt();
 | 
			
		||||
							}}
 | 
			
		||||
						>
 | 
			
		||||
							<div class="flex items-center line-clamp-1">Plain text (.txt)</div>
 | 
			
		||||
						</DropdownMenu.Item>
 | 
			
		||||
 | 
			
		||||
						<DropdownMenu.Item
 | 
			
		||||
							class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-850 rounded-md"
 | 
			
		||||
							on:click={() => {
 | 
			
		||||
								downloadChatAsPdf();
 | 
			
		||||
							}}
 | 
			
		||||
						>
 | 
			
		||||
							<div class="flex items-center line-clamp-1">PDF document (.pdf)</div>
 | 
			
		||||
						</DropdownMenu.Item>
 | 
			
		||||
					</DropdownMenu.SubContent>
 | 
			
		||||
				</DropdownMenu.Sub>
 | 
			
		||||
 | 
			
		||||
				<hr class="border-gray-100 dark:border-gray-800 mt-2.5 mb-1.5" />
 | 
			
		||||
 | 
			
		||||
				<div class="flex p-1">
 | 
			
		||||
					<Tags {tags} {deleteTag} {addTag} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue