forked from open-webui/open-webui
		
	feat: add frontend support for locally sharing chats
This commit is contained in:
		
							parent
							
								
									94976e5ed3
								
							
						
					
					
						commit
						7978adbf45
					
				
					 9 changed files with 433 additions and 106 deletions
				
			
		|  | @ -218,6 +218,102 @@ export const getChatById = async (token: string, id: string) => { | |||
| 	return res; | ||||
| }; | ||||
| 
 | ||||
| export const getChatByShareId = async (token: string, share_id: string) => { | ||||
| 	let error = null; | ||||
| 
 | ||||
| 	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/share/${share_id}`, { | ||||
| 		method: 'GET', | ||||
| 		headers: { | ||||
| 			Accept: 'application/json', | ||||
| 			'Content-Type': 'application/json', | ||||
| 			...(token && { authorization: `Bearer ${token}` }) | ||||
| 		} | ||||
| 	}) | ||||
| 		.then(async (res) => { | ||||
| 			if (!res.ok) throw await res.json(); | ||||
| 			return res.json(); | ||||
| 		}) | ||||
| 		.then((json) => { | ||||
| 			return json; | ||||
| 		}) | ||||
| 		.catch((err) => { | ||||
| 			error = err; | ||||
| 
 | ||||
| 			console.log(err); | ||||
| 			return null; | ||||
| 		}); | ||||
| 
 | ||||
| 	if (error) { | ||||
| 		throw error; | ||||
| 	} | ||||
| 
 | ||||
| 	return res; | ||||
| }; | ||||
| 
 | ||||
| export const shareChatById = async (token: string, id: string) => { | ||||
| 	let error = null; | ||||
| 
 | ||||
| 	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/share`, { | ||||
| 		method: 'POST', | ||||
| 		headers: { | ||||
| 			Accept: 'application/json', | ||||
| 			'Content-Type': 'application/json', | ||||
| 			...(token && { authorization: `Bearer ${token}` }) | ||||
| 		} | ||||
| 	}) | ||||
| 		.then(async (res) => { | ||||
| 			if (!res.ok) throw await res.json(); | ||||
| 			return res.json(); | ||||
| 		}) | ||||
| 		.then((json) => { | ||||
| 			return json; | ||||
| 		}) | ||||
| 		.catch((err) => { | ||||
| 			error = err; | ||||
| 
 | ||||
| 			console.log(err); | ||||
| 			return null; | ||||
| 		}); | ||||
| 
 | ||||
| 	if (error) { | ||||
| 		throw error; | ||||
| 	} | ||||
| 
 | ||||
| 	return res; | ||||
| }; | ||||
| 
 | ||||
| export const deleteSharedChatById = async (token: string, id: string) => { | ||||
| 	let error = null; | ||||
| 
 | ||||
| 	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}`, { | ||||
| 		method: 'DELETE', | ||||
| 		headers: { | ||||
| 			Accept: 'application/json', | ||||
| 			'Content-Type': 'application/json', | ||||
| 			...(token && { authorization: `Bearer ${token}` }) | ||||
| 		} | ||||
| 	}) | ||||
| 		.then(async (res) => { | ||||
| 			if (!res.ok) throw await res.json(); | ||||
| 			return res.json(); | ||||
| 		}) | ||||
| 		.then((json) => { | ||||
| 			return json; | ||||
| 		}) | ||||
| 		.catch((err) => { | ||||
| 			error = err; | ||||
| 
 | ||||
| 			console.log(err); | ||||
| 			return null; | ||||
| 		}); | ||||
| 
 | ||||
| 	if (error) { | ||||
| 		throw error; | ||||
| 	} | ||||
| 
 | ||||
| 	return res; | ||||
| }; | ||||
| 
 | ||||
| export const updateChatById = async (token: string, id: string, chat: object) => { | ||||
| 	let error = null; | ||||
| 
 | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ | |||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| 	export let chatId = ''; | ||||
| 	export let readOnly = false; | ||||
| 	export let sendPrompt: Function; | ||||
| 	export let continueGeneration: Function; | ||||
| 	export let regenerateResponse: Function; | ||||
|  | @ -317,6 +318,7 @@ | |||
| 							<UserMessage | ||||
| 								on:delete={() => messageDeleteHandler(message.id)} | ||||
| 								user={$user} | ||||
| 								{readOnly} | ||||
| 								{message} | ||||
| 								isFirstMessage={messageIdx === 0} | ||||
| 								siblings={message.parentId !== null | ||||
|  | @ -335,6 +337,7 @@ | |||
| 								modelfiles={selectedModelfiles} | ||||
| 								siblings={history.messages[message.parentId]?.childrenIds ?? []} | ||||
| 								isLastMessage={messageIdx + 1 === messages.length} | ||||
| 								{readOnly} | ||||
| 								{confirmEditResponseMessage} | ||||
| 								{showPreviousMessage} | ||||
| 								{showNextMessage} | ||||
|  |  | |||
|  | @ -33,6 +33,8 @@ | |||
| 
 | ||||
| 	export let isLastMessage = true; | ||||
| 
 | ||||
| 	export let readOnly = false; | ||||
| 
 | ||||
| 	export let confirmEditResponseMessage: Function; | ||||
| 	export let showPreviousMessage: Function; | ||||
| 	export let showNextMessage: Function; | ||||
|  | @ -469,31 +471,33 @@ | |||
| 											</div> | ||||
| 										{/if} | ||||
| 
 | ||||
| 										<Tooltip content="Edit" placement="bottom"> | ||||
| 											<button | ||||
| 												class="{isLastMessage | ||||
| 													? 'visible' | ||||
| 													: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition" | ||||
| 												on:click={() => { | ||||
| 													editMessageHandler(); | ||||
| 												}} | ||||
| 											> | ||||
| 												<svg | ||||
| 													xmlns="http://www.w3.org/2000/svg" | ||||
| 													fill="none" | ||||
| 													viewBox="0 0 24 24" | ||||
| 													stroke-width="2" | ||||
| 													stroke="currentColor" | ||||
| 													class="w-4 h-4" | ||||
| 										{#if !readOnly} | ||||
| 											<Tooltip content="Edit" placement="bottom"> | ||||
| 												<button | ||||
| 													class="{isLastMessage | ||||
| 														? 'visible' | ||||
| 														: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition" | ||||
| 													on:click={() => { | ||||
| 														editMessageHandler(); | ||||
| 													}} | ||||
| 												> | ||||
| 													<path | ||||
| 														stroke-linecap="round" | ||||
| 														stroke-linejoin="round" | ||||
| 														d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" | ||||
| 													/> | ||||
| 												</svg> | ||||
| 											</button> | ||||
| 										</Tooltip> | ||||
| 													<svg | ||||
| 														xmlns="http://www.w3.org/2000/svg" | ||||
| 														fill="none" | ||||
| 														viewBox="0 0 24 24" | ||||
| 														stroke-width="2" | ||||
| 														stroke="currentColor" | ||||
| 														class="w-4 h-4" | ||||
| 													> | ||||
| 														<path | ||||
| 															stroke-linecap="round" | ||||
| 															stroke-linejoin="round" | ||||
| 															d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" | ||||
| 														/> | ||||
| 													</svg> | ||||
| 												</button> | ||||
| 											</Tooltip> | ||||
| 										{/if} | ||||
| 
 | ||||
| 										<Tooltip content="Copy" placement="bottom"> | ||||
| 											<button | ||||
|  | @ -521,59 +525,61 @@ | |||
| 											</button> | ||||
| 										</Tooltip> | ||||
| 
 | ||||
| 										<Tooltip content="Good Response" placement="bottom"> | ||||
| 											<button | ||||
| 												class="{isLastMessage | ||||
| 													? 'visible' | ||||
| 													: 'invisible group-hover:visible'} p-1 rounded {message.rating === 1 | ||||
| 													? 'bg-gray-100 dark:bg-gray-800' | ||||
| 													: ''} dark:hover:text-white hover:text-black transition" | ||||
| 												on:click={() => { | ||||
| 													rateMessage(message.id, 1); | ||||
| 												}} | ||||
| 											> | ||||
| 												<svg | ||||
| 													stroke="currentColor" | ||||
| 													fill="none" | ||||
| 													stroke-width="2" | ||||
| 													viewBox="0 0 24 24" | ||||
| 													stroke-linecap="round" | ||||
| 													stroke-linejoin="round" | ||||
| 													class="w-4 h-4" | ||||
| 													xmlns="http://www.w3.org/2000/svg" | ||||
| 													><path | ||||
| 														d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" | ||||
| 													/></svg | ||||
| 										{#if !readOnly} | ||||
| 											<Tooltip content="Good Response" placement="bottom"> | ||||
| 												<button | ||||
| 													class="{isLastMessage | ||||
| 														? 'visible' | ||||
| 														: 'invisible group-hover:visible'} p-1 rounded {message.rating === 1 | ||||
| 														? 'bg-gray-100 dark:bg-gray-800' | ||||
| 														: ''} dark:hover:text-white hover:text-black transition" | ||||
| 													on:click={() => { | ||||
| 														rateMessage(message.id, 1); | ||||
| 													}} | ||||
| 												> | ||||
| 											</button> | ||||
| 										</Tooltip> | ||||
| 													<svg | ||||
| 														stroke="currentColor" | ||||
| 														fill="none" | ||||
| 														stroke-width="2" | ||||
| 														viewBox="0 0 24 24" | ||||
| 														stroke-linecap="round" | ||||
| 														stroke-linejoin="round" | ||||
| 														class="w-4 h-4" | ||||
| 														xmlns="http://www.w3.org/2000/svg" | ||||
| 														><path | ||||
| 															d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" | ||||
| 														/></svg | ||||
| 													> | ||||
| 												</button> | ||||
| 											</Tooltip> | ||||
| 
 | ||||
| 										<Tooltip content="Bad Response" placement="bottom"> | ||||
| 											<button | ||||
| 												class="{isLastMessage | ||||
| 													? 'visible' | ||||
| 													: 'invisible group-hover:visible'} p-1 rounded {message.rating === -1 | ||||
| 													? 'bg-gray-100 dark:bg-gray-800' | ||||
| 													: ''} dark:hover:text-white hover:text-black transition" | ||||
| 												on:click={() => { | ||||
| 													rateMessage(message.id, -1); | ||||
| 												}} | ||||
| 											> | ||||
| 												<svg | ||||
| 													stroke="currentColor" | ||||
| 													fill="none" | ||||
| 													stroke-width="2" | ||||
| 													viewBox="0 0 24 24" | ||||
| 													stroke-linecap="round" | ||||
| 													stroke-linejoin="round" | ||||
| 													class="w-4 h-4" | ||||
| 													xmlns="http://www.w3.org/2000/svg" | ||||
| 													><path | ||||
| 														d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" | ||||
| 													/></svg | ||||
| 											<Tooltip content="Bad Response" placement="bottom"> | ||||
| 												<button | ||||
| 													class="{isLastMessage | ||||
| 														? 'visible' | ||||
| 														: 'invisible group-hover:visible'} p-1 rounded {message.rating === -1 | ||||
| 														? 'bg-gray-100 dark:bg-gray-800' | ||||
| 														: ''} dark:hover:text-white hover:text-black transition" | ||||
| 													on:click={() => { | ||||
| 														rateMessage(message.id, -1); | ||||
| 													}} | ||||
| 												> | ||||
| 											</button> | ||||
| 										</Tooltip> | ||||
| 													<svg | ||||
| 														stroke="currentColor" | ||||
| 														fill="none" | ||||
| 														stroke-width="2" | ||||
| 														viewBox="0 0 24 24" | ||||
| 														stroke-linecap="round" | ||||
| 														stroke-linejoin="round" | ||||
| 														class="w-4 h-4" | ||||
| 														xmlns="http://www.w3.org/2000/svg" | ||||
| 														><path | ||||
| 															d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" | ||||
| 														/></svg | ||||
| 													> | ||||
| 												</button> | ||||
| 											</Tooltip> | ||||
| 										{/if} | ||||
| 
 | ||||
| 										<Tooltip content="Read Aloud" placement="bottom"> | ||||
| 											<button | ||||
|  | @ -656,7 +662,7 @@ | |||
| 											</button> | ||||
| 										</Tooltip> | ||||
| 
 | ||||
| 										{#if $config.images} | ||||
| 										{#if $config.images && !readOnly} | ||||
| 											<Tooltip content="Generate Image" placement="bottom"> | ||||
| 												<button | ||||
| 													class="{isLastMessage | ||||
|  | @ -752,7 +758,7 @@ | |||
| 											</Tooltip> | ||||
| 										{/if} | ||||
| 
 | ||||
| 										{#if isLastMessage} | ||||
| 										{#if isLastMessage && !readOnly} | ||||
| 											<Tooltip content="Continue Response" placement="bottom"> | ||||
| 												<button | ||||
| 													type="button" | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ | |||
| 	export let message; | ||||
| 	export let siblings; | ||||
| 	export let isFirstMessage: boolean; | ||||
| 	export let readOnly: boolean; | ||||
| 
 | ||||
| 	export let confirmEditMessage: Function; | ||||
| 	export let showPreviousMessage: Function; | ||||
|  | @ -250,29 +251,31 @@ | |||
| 							</div> | ||||
| 						{/if} | ||||
| 
 | ||||
| 						<Tooltip content="Edit" placement="bottom"> | ||||
| 							<button | ||||
| 								class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button" | ||||
| 								on:click={() => { | ||||
| 									editMessageHandler(); | ||||
| 								}} | ||||
| 							> | ||||
| 								<svg | ||||
| 									xmlns="http://www.w3.org/2000/svg" | ||||
| 									fill="none" | ||||
| 									viewBox="0 0 24 24" | ||||
| 									stroke-width="2" | ||||
| 									stroke="currentColor" | ||||
| 									class="w-4 h-4" | ||||
| 						{#if !readOnly} | ||||
| 							<Tooltip content="Edit" placement="bottom"> | ||||
| 								<button | ||||
| 									class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button" | ||||
| 									on:click={() => { | ||||
| 										editMessageHandler(); | ||||
| 									}} | ||||
| 								> | ||||
| 									<path | ||||
| 										stroke-linecap="round" | ||||
| 										stroke-linejoin="round" | ||||
| 										d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" | ||||
| 									/> | ||||
| 								</svg> | ||||
| 							</button> | ||||
| 						</Tooltip> | ||||
| 									<svg | ||||
| 										xmlns="http://www.w3.org/2000/svg" | ||||
| 										fill="none" | ||||
| 										viewBox="0 0 24 24" | ||||
| 										stroke-width="2" | ||||
| 										stroke="currentColor" | ||||
| 										class="w-4 h-4" | ||||
| 									> | ||||
| 										<path | ||||
| 											stroke-linecap="round" | ||||
| 											stroke-linejoin="round" | ||||
| 											d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" | ||||
| 										/> | ||||
| 									</svg> | ||||
| 								</button> | ||||
| 							</Tooltip> | ||||
| 						{/if} | ||||
| 
 | ||||
| 						<Tooltip content="Copy" placement="bottom"> | ||||
| 							<button | ||||
|  | @ -298,7 +301,7 @@ | |||
| 							</button> | ||||
| 						</Tooltip> | ||||
| 
 | ||||
| 						{#if !isFirstMessage} | ||||
| 						{#if !isFirstMessage && !readOnly} | ||||
| 							<Tooltip content="Delete" placement="bottom"> | ||||
| 								<button | ||||
| 									class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition" | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| 
 | ||||
| 	export let downloadChat: Function; | ||||
| 	export let shareChat: Function; | ||||
| 	export let shareLocalChat: Function; | ||||
| 
 | ||||
| 	export let show = false; | ||||
| </script> | ||||
|  | @ -23,6 +24,17 @@ | |||
| 			{$i18n.t('Share to OpenWebUI Community')} | ||||
| 		</button> | ||||
| 
 | ||||
| 		<button | ||||
| 			class=" self-center px-8 py-1.5 w-full rounded-full text-sm font-medium bg-blue-600 hover:bg-blue-500 text-white mt-1.5" | ||||
| 			type="button" | ||||
| 			on:click={() => { | ||||
| 				shareLocalChat(); | ||||
| 				show = false; | ||||
| 			}} | ||||
| 		> | ||||
| 			{$i18n.t('Create local share link')} | ||||
| 		</button> | ||||
| 
 | ||||
| 		<div class="flex justify-center space-x-1 mt-1.5"> | ||||
| 			<div class=" self-center text-gray-400 text-xs font-medium">{$i18n.t('or')}</div> | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ | |||
| 	const { saveAs } = fileSaver; | ||||
| 
 | ||||
| 	import { Separator } from 'bits-ui'; | ||||
| 	import { getChatById } from '$lib/apis/chats'; | ||||
| 	import { getChatById, shareChatById } from '$lib/apis/chats'; | ||||
| 	import { WEBUI_NAME, chatId, modelfiles, settings, showSettings } from '$lib/stores'; | ||||
| 
 | ||||
| 	import { slide } from 'svelte/transition'; | ||||
|  | @ -19,6 +19,7 @@ | |||
| 	import ChevronUpDown from '../icons/ChevronUpDown.svelte'; | ||||
| 	import Menu from './Navbar/Menu.svelte'; | ||||
| 	import TagChatModal from '../chat/TagChatModal.svelte'; | ||||
| 	import { copyToClipboard } from '$lib/utils'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
|  | @ -32,7 +33,7 @@ | |||
| 	export let addTag: Function; | ||||
| 	export let deleteTag: Function; | ||||
| 
 | ||||
| 	export let showModelSelector = false; | ||||
| 	export let showModelSelector = true; | ||||
| 
 | ||||
| 	let showShareChatModal = false; | ||||
| 	let showTagChatModal = false; | ||||
|  | @ -64,6 +65,23 @@ | |||
| 		); | ||||
| 	}; | ||||
| 
 | ||||
| 	const shareLocalChat = async () => { | ||||
| 		const chat = await getChatById(localStorage.token, $chatId); | ||||
| 		console.log('shareLocal', chat); | ||||
| 		if (chat.share_id) { | ||||
| 			const shareUrl = `${window.location.origin}/s/${chat.share_id}`; | ||||
| 			toast.info( | ||||
| 				$i18n.t('Chat is already shared at {{shareUrl}}, copied to clipboard', { shareUrl }) | ||||
| 			); | ||||
| 			copyToClipboard(shareUrl); | ||||
| 		} else { | ||||
| 			const sharedChat = await shareChatById(localStorage.token, $chatId); | ||||
| 			const shareUrl = `${window.location.origin}/s/${sharedChat.id}`; | ||||
| 			toast.info($i18n.t('Chat is now shared at {{shareUrl}}, copied to clipboard', { shareUrl })); | ||||
| 			copyToClipboard(shareUrl); | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	const downloadChat = async () => { | ||||
| 		const chat = (await getChatById(localStorage.token, $chatId)).chat; | ||||
| 		console.log('download', chat); | ||||
|  | @ -80,7 +98,7 @@ | |||
| 	}; | ||||
| </script> | ||||
| 
 | ||||
| <ShareChatModal bind:show={showShareChatModal} {downloadChat} {shareChat} /> | ||||
| <ShareChatModal bind:show={showShareChatModal} {downloadChat} {shareChat} {shareLocalChat} /> | ||||
| <!-- <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 | ||||
|  | @ -135,8 +153,10 @@ | |||
| 		</div> --> | ||||
| 
 | ||||
| 		<div class="flex items-center w-full max-w-full"> | ||||
| 			<div class="w-full flex-1 overflow-hidden max-w-full"> | ||||
| 				<ModelSelector bind:selectedModels /> | ||||
| 			<div class="flex-1 overflow-hidden max-w-full"> | ||||
| 				{#if showModelSelector} | ||||
| 					<ModelSelector bind:selectedModels /> | ||||
| 				{/if} | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div class="self-start flex flex-none items-center"> | ||||
|  |  | |||
|  | @ -48,7 +48,7 @@ | |||
| 	let messagesContainerElement: HTMLDivElement; | ||||
| 	let currentRequestId = null; | ||||
| 
 | ||||
| 	let showModelSelector = false; | ||||
| 	let showModelSelector = true; | ||||
| 	let selectedModels = ['']; | ||||
| 
 | ||||
| 	let selectedModelfile = null; | ||||
|  |  | |||
|  | @ -56,7 +56,7 @@ | |||
| 	let currentRequestId = null; | ||||
| 
 | ||||
| 	// let chatId = $page.params.id; | ||||
| 	let showModelSelector = false; | ||||
| 	let showModelSelector = true; | ||||
| 	let selectedModels = ['']; | ||||
| 	let selectedModelfile = null; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										187
									
								
								src/routes/(app)/s/[id]/+page.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								src/routes/(app)/s/[id]/+page.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,187 @@ | |||
| <script lang="ts"> | ||||
| 	import { onMount, tick, getContext } from 'svelte'; | ||||
| 	import { goto } from '$app/navigation'; | ||||
| 	import { page } from '$app/stores'; | ||||
| 
 | ||||
| 	import { modelfiles, settings, chatId, WEBUI_NAME } from '$lib/stores'; | ||||
| 	import { convertMessagesToHistory } from '$lib/utils'; | ||||
| 
 | ||||
| 	import { getChatByShareId } from '$lib/apis/chats'; | ||||
| 
 | ||||
| 	import Messages from '$lib/components/chat/Messages.svelte'; | ||||
| 	import Navbar from '$lib/components/layout/Navbar.svelte'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| 	let loaded = false; | ||||
| 
 | ||||
| 	let autoScroll = true; | ||||
| 	let processing = ''; | ||||
| 	let messagesContainerElement: HTMLDivElement; | ||||
| 
 | ||||
| 	// let chatId = $page.params.id; | ||||
| 	let showModelSelector = false; | ||||
| 	let selectedModels = ['']; | ||||
| 
 | ||||
| 	let selectedModelfiles = {}; | ||||
| 	$: selectedModelfiles = selectedModels.reduce((a, tagName, i, arr) => { | ||||
| 		const modelfile = | ||||
| 			$modelfiles.filter((modelfile) => modelfile.tagName === tagName)?.at(0) ?? undefined; | ||||
| 
 | ||||
| 		return { | ||||
| 			...a, | ||||
| 			...(modelfile && { [tagName]: modelfile }) | ||||
| 		}; | ||||
| 	}, {}); | ||||
| 
 | ||||
| 	let chat = null; | ||||
| 
 | ||||
| 	let title = ''; | ||||
| 	let files = []; | ||||
| 
 | ||||
| 	let messages = []; | ||||
| 	let history = { | ||||
| 		messages: {}, | ||||
| 		currentId: null | ||||
| 	}; | ||||
| 
 | ||||
| 	$: if (history.currentId !== null) { | ||||
| 		let _messages = []; | ||||
| 
 | ||||
| 		let currentMessage = history.messages[history.currentId]; | ||||
| 		while (currentMessage !== null) { | ||||
| 			_messages.unshift({ ...currentMessage }); | ||||
| 			currentMessage = | ||||
| 				currentMessage.parentId !== null ? history.messages[currentMessage.parentId] : null; | ||||
| 		} | ||||
| 		messages = _messages; | ||||
| 	} else { | ||||
| 		messages = []; | ||||
| 	} | ||||
| 
 | ||||
| 	$: if ($page.params.id) { | ||||
| 		(async () => { | ||||
| 			if (await loadSharedChat()) { | ||||
| 				await tick(); | ||||
| 				loaded = true; | ||||
| 
 | ||||
| 				window.setTimeout(() => scrollToBottom(), 0); | ||||
| 				const chatInput = document.getElementById('chat-textarea'); | ||||
| 				chatInput?.focus(); | ||||
| 			} else { | ||||
| 				await goto('/'); | ||||
| 			} | ||||
| 		})(); | ||||
| 	} | ||||
| 
 | ||||
| 	////////////////////////// | ||||
| 	// Web functions | ||||
| 	////////////////////////// | ||||
| 
 | ||||
| 	const loadSharedChat = async () => { | ||||
| 		await chatId.set($page.params.id); | ||||
| 		chat = await getChatByShareId(localStorage.token, $chatId).catch(async (error) => { | ||||
| 			await goto('/'); | ||||
| 			return null; | ||||
| 		}); | ||||
| 
 | ||||
| 		if (chat) { | ||||
| 			const chatContent = chat.chat; | ||||
| 
 | ||||
| 			if (chatContent) { | ||||
| 				console.log(chatContent); | ||||
| 
 | ||||
| 				selectedModels = | ||||
| 					(chatContent?.models ?? undefined) !== undefined | ||||
| 						? chatContent.models | ||||
| 						: [chatContent.models ?? '']; | ||||
| 				history = | ||||
| 					(chatContent?.history ?? undefined) !== undefined | ||||
| 						? chatContent.history | ||||
| 						: convertMessagesToHistory(chatContent.messages); | ||||
| 				title = chatContent.title; | ||||
| 
 | ||||
| 				let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}'); | ||||
| 				await settings.set({ | ||||
| 					..._settings, | ||||
| 					system: chatContent.system ?? _settings.system, | ||||
| 					options: chatContent.options ?? _settings.options | ||||
| 				}); | ||||
| 				autoScroll = true; | ||||
| 				await tick(); | ||||
| 
 | ||||
| 				if (messages.length > 0) { | ||||
| 					history.messages[messages.at(-1).id].done = true; | ||||
| 				} | ||||
| 				await tick(); | ||||
| 
 | ||||
| 				return true; | ||||
| 			} else { | ||||
| 				return null; | ||||
| 			} | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	const scrollToBottom = () => { | ||||
| 		if (messagesContainerElement) { | ||||
| 			messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight; | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	onMount(async () => { | ||||
| 		if (!($settings.saveChatHistory ?? true)) { | ||||
| 			await goto('/'); | ||||
| 		} | ||||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| <svelte:head> | ||||
| 	<title> | ||||
| 		{title | ||||
| 			? `${title.length > 30 ? `${title.slice(0, 30)}...` : title} | ${$WEBUI_NAME}` | ||||
| 			: `${$WEBUI_NAME}`} | ||||
| 	</title> | ||||
| </svelte:head> | ||||
| 
 | ||||
| {#if loaded} | ||||
| 	<div class="min-h-screen max-h-screen w-full flex flex-col"> | ||||
| 		<Navbar | ||||
| 			{title} | ||||
| 			bind:selectedModels | ||||
| 			bind:showModelSelector | ||||
| 			shareEnabled={false} | ||||
| 			initNewChat={async () => { | ||||
| 				goto('/'); | ||||
| 			}} | ||||
| 		/> | ||||
| 		<div class="flex flex-col flex-auto"> | ||||
| 			<div | ||||
| 				class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0" | ||||
| 				id="messages-container" | ||||
| 				bind:this={messagesContainerElement} | ||||
| 				on:scroll={(e) => { | ||||
| 					autoScroll = | ||||
| 						messagesContainerElement.scrollHeight - messagesContainerElement.scrollTop <= | ||||
| 						messagesContainerElement.clientHeight + 5; | ||||
| 				}} | ||||
| 			> | ||||
| 				<div class=" h-full w-full flex flex-col py-4"> | ||||
| 					<Messages | ||||
| 						chatId={$chatId} | ||||
| 						readOnly={true} | ||||
| 						{selectedModels} | ||||
| 						{selectedModelfiles} | ||||
| 						{processing} | ||||
| 						bind:history | ||||
| 						bind:messages | ||||
| 						bind:autoScroll | ||||
| 						bottomPadding={files.length > 0} | ||||
| 						sendPrompt={() => {}} | ||||
| 						continueGeneration={() => {}} | ||||
| 						regenerateResponse={() => {}} | ||||
| 					/> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| {/if} | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jun Siang Cheah
						Jun Siang Cheah