forked from open-webui/open-webui
		
	feat: add frontend support for locally sharing chats
This commit is contained in:
		
							parent
							
								
									bfbfdae1c5
								
							
						
					
					
						commit
						196f91d68c
					
				
					 9 changed files with 433 additions and 106 deletions
				
			
		|  | @ -218,6 +218,102 @@ export const getChatById = async (token: string, id: string) => { | ||||||
| 	return res; | 	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) => { | export const updateChatById = async (token: string, id: string, chat: object) => { | ||||||
| 	let error = null; | 	let error = null; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ | ||||||
| 	const i18n = getContext('i18n'); | 	const i18n = getContext('i18n'); | ||||||
| 
 | 
 | ||||||
| 	export let chatId = ''; | 	export let chatId = ''; | ||||||
|  | 	export let readOnly = false; | ||||||
| 	export let sendPrompt: Function; | 	export let sendPrompt: Function; | ||||||
| 	export let continueGeneration: Function; | 	export let continueGeneration: Function; | ||||||
| 	export let regenerateResponse: Function; | 	export let regenerateResponse: Function; | ||||||
|  | @ -317,6 +318,7 @@ | ||||||
| 							<UserMessage | 							<UserMessage | ||||||
| 								on:delete={() => messageDeleteHandler(message.id)} | 								on:delete={() => messageDeleteHandler(message.id)} | ||||||
| 								user={$user} | 								user={$user} | ||||||
|  | 								{readOnly} | ||||||
| 								{message} | 								{message} | ||||||
| 								isFirstMessage={messageIdx === 0} | 								isFirstMessage={messageIdx === 0} | ||||||
| 								siblings={message.parentId !== null | 								siblings={message.parentId !== null | ||||||
|  | @ -335,6 +337,7 @@ | ||||||
| 								modelfiles={selectedModelfiles} | 								modelfiles={selectedModelfiles} | ||||||
| 								siblings={history.messages[message.parentId]?.childrenIds ?? []} | 								siblings={history.messages[message.parentId]?.childrenIds ?? []} | ||||||
| 								isLastMessage={messageIdx + 1 === messages.length} | 								isLastMessage={messageIdx + 1 === messages.length} | ||||||
|  | 								{readOnly} | ||||||
| 								{confirmEditResponseMessage} | 								{confirmEditResponseMessage} | ||||||
| 								{showPreviousMessage} | 								{showPreviousMessage} | ||||||
| 								{showNextMessage} | 								{showNextMessage} | ||||||
|  |  | ||||||
|  | @ -33,6 +33,8 @@ | ||||||
| 
 | 
 | ||||||
| 	export let isLastMessage = true; | 	export let isLastMessage = true; | ||||||
| 
 | 
 | ||||||
|  | 	export let readOnly = false; | ||||||
|  | 
 | ||||||
| 	export let confirmEditResponseMessage: Function; | 	export let confirmEditResponseMessage: Function; | ||||||
| 	export let showPreviousMessage: Function; | 	export let showPreviousMessage: Function; | ||||||
| 	export let showNextMessage: Function; | 	export let showNextMessage: Function; | ||||||
|  | @ -469,31 +471,33 @@ | ||||||
| 											</div> | 											</div> | ||||||
| 										{/if} | 										{/if} | ||||||
| 
 | 
 | ||||||
| 										<Tooltip content="Edit" placement="bottom"> | 										{#if !readOnly} | ||||||
| 											<button | 											<Tooltip content="Edit" placement="bottom"> | ||||||
| 												class="{isLastMessage | 												<button | ||||||
| 													? 'visible' | 													class="{isLastMessage | ||||||
| 													: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition" | 														? 'visible' | ||||||
| 												on:click={() => { | 														: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition" | ||||||
| 													editMessageHandler(); | 													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" |  | ||||||
| 												> | 												> | ||||||
| 													<path | 													<svg | ||||||
| 														stroke-linecap="round" | 														xmlns="http://www.w3.org/2000/svg" | ||||||
| 														stroke-linejoin="round" | 														fill="none" | ||||||
| 														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" | 														viewBox="0 0 24 24" | ||||||
| 													/> | 														stroke-width="2" | ||||||
| 												</svg> | 														stroke="currentColor" | ||||||
| 											</button> | 														class="w-4 h-4" | ||||||
| 										</Tooltip> | 													> | ||||||
|  | 														<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"> | 										<Tooltip content="Copy" placement="bottom"> | ||||||
| 											<button | 											<button | ||||||
|  | @ -521,59 +525,61 @@ | ||||||
| 											</button> | 											</button> | ||||||
| 										</Tooltip> | 										</Tooltip> | ||||||
| 
 | 
 | ||||||
| 										<Tooltip content="Good Response" placement="bottom"> | 										{#if !readOnly} | ||||||
| 											<button | 											<Tooltip content="Good Response" placement="bottom"> | ||||||
| 												class="{isLastMessage | 												<button | ||||||
| 													? 'visible' | 													class="{isLastMessage | ||||||
| 													: 'invisible group-hover:visible'} p-1 rounded {message.rating === 1 | 														? 'visible' | ||||||
| 													? 'bg-gray-100 dark:bg-gray-800' | 														: 'invisible group-hover:visible'} p-1 rounded {message.rating === 1 | ||||||
| 													: ''} dark:hover:text-white hover:text-black transition" | 														? 'bg-gray-100 dark:bg-gray-800' | ||||||
| 												on:click={() => { | 														: ''} dark:hover:text-white hover:text-black transition" | ||||||
| 													rateMessage(message.id, 1); | 													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 |  | ||||||
| 												> | 												> | ||||||
| 											</button> | 													<svg | ||||||
| 										</Tooltip> | 														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"> | 											<Tooltip content="Bad Response" placement="bottom"> | ||||||
| 											<button | 												<button | ||||||
| 												class="{isLastMessage | 													class="{isLastMessage | ||||||
| 													? 'visible' | 														? 'visible' | ||||||
| 													: 'invisible group-hover:visible'} p-1 rounded {message.rating === -1 | 														: 'invisible group-hover:visible'} p-1 rounded {message.rating === -1 | ||||||
| 													? 'bg-gray-100 dark:bg-gray-800' | 														? 'bg-gray-100 dark:bg-gray-800' | ||||||
| 													: ''} dark:hover:text-white hover:text-black transition" | 														: ''} dark:hover:text-white hover:text-black transition" | ||||||
| 												on:click={() => { | 													on:click={() => { | ||||||
| 													rateMessage(message.id, -1); | 														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 |  | ||||||
| 												> | 												> | ||||||
| 											</button> | 													<svg | ||||||
| 										</Tooltip> | 														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"> | 										<Tooltip content="Read Aloud" placement="bottom"> | ||||||
| 											<button | 											<button | ||||||
|  | @ -656,7 +662,7 @@ | ||||||
| 											</button> | 											</button> | ||||||
| 										</Tooltip> | 										</Tooltip> | ||||||
| 
 | 
 | ||||||
| 										{#if $config.images} | 										{#if $config.images && !readOnly} | ||||||
| 											<Tooltip content="Generate Image" placement="bottom"> | 											<Tooltip content="Generate Image" placement="bottom"> | ||||||
| 												<button | 												<button | ||||||
| 													class="{isLastMessage | 													class="{isLastMessage | ||||||
|  | @ -752,7 +758,7 @@ | ||||||
| 											</Tooltip> | 											</Tooltip> | ||||||
| 										{/if} | 										{/if} | ||||||
| 
 | 
 | ||||||
| 										{#if isLastMessage} | 										{#if isLastMessage && !readOnly} | ||||||
| 											<Tooltip content="Continue Response" placement="bottom"> | 											<Tooltip content="Continue Response" placement="bottom"> | ||||||
| 												<button | 												<button | ||||||
| 													type="button" | 													type="button" | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ | ||||||
| 	export let message; | 	export let message; | ||||||
| 	export let siblings; | 	export let siblings; | ||||||
| 	export let isFirstMessage: boolean; | 	export let isFirstMessage: boolean; | ||||||
|  | 	export let readOnly: boolean; | ||||||
| 
 | 
 | ||||||
| 	export let confirmEditMessage: Function; | 	export let confirmEditMessage: Function; | ||||||
| 	export let showPreviousMessage: Function; | 	export let showPreviousMessage: Function; | ||||||
|  | @ -250,29 +251,31 @@ | ||||||
| 							</div> | 							</div> | ||||||
| 						{/if} | 						{/if} | ||||||
| 
 | 
 | ||||||
| 						<Tooltip content="Edit" placement="bottom"> | 						{#if !readOnly} | ||||||
| 							<button | 							<Tooltip content="Edit" placement="bottom"> | ||||||
| 								class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button" | 								<button | ||||||
| 								on:click={() => { | 									class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button" | ||||||
| 									editMessageHandler(); | 									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" |  | ||||||
| 								> | 								> | ||||||
| 									<path | 									<svg | ||||||
| 										stroke-linecap="round" | 										xmlns="http://www.w3.org/2000/svg" | ||||||
| 										stroke-linejoin="round" | 										fill="none" | ||||||
| 										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" | 										viewBox="0 0 24 24" | ||||||
| 									/> | 										stroke-width="2" | ||||||
| 								</svg> | 										stroke="currentColor" | ||||||
| 							</button> | 										class="w-4 h-4" | ||||||
| 						</Tooltip> | 									> | ||||||
|  | 										<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"> | 						<Tooltip content="Copy" placement="bottom"> | ||||||
| 							<button | 							<button | ||||||
|  | @ -298,7 +301,7 @@ | ||||||
| 							</button> | 							</button> | ||||||
| 						</Tooltip> | 						</Tooltip> | ||||||
| 
 | 
 | ||||||
| 						{#if !isFirstMessage} | 						{#if !isFirstMessage && !readOnly} | ||||||
| 							<Tooltip content="Delete" placement="bottom"> | 							<Tooltip content="Delete" placement="bottom"> | ||||||
| 								<button | 								<button | ||||||
| 									class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition" | 									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 downloadChat: Function; | ||||||
| 	export let shareChat: Function; | 	export let shareChat: Function; | ||||||
|  | 	export let shareLocalChat: Function; | ||||||
| 
 | 
 | ||||||
| 	export let show = false; | 	export let show = false; | ||||||
| </script> | </script> | ||||||
|  | @ -23,6 +24,17 @@ | ||||||
| 			{$i18n.t('Share to OpenWebUI Community')} | 			{$i18n.t('Share to OpenWebUI Community')} | ||||||
| 		</button> | 		</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="flex justify-center space-x-1 mt-1.5"> | ||||||
| 			<div class=" self-center text-gray-400 text-xs font-medium">{$i18n.t('or')}</div> | 			<div class=" self-center text-gray-400 text-xs font-medium">{$i18n.t('or')}</div> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ | ||||||
| 	const { saveAs } = fileSaver; | 	const { saveAs } = fileSaver; | ||||||
| 
 | 
 | ||||||
| 	import { Separator } from 'bits-ui'; | 	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 { WEBUI_NAME, chatId, modelfiles, settings, showSettings } from '$lib/stores'; | ||||||
| 
 | 
 | ||||||
| 	import { slide } from 'svelte/transition'; | 	import { slide } from 'svelte/transition'; | ||||||
|  | @ -19,6 +19,7 @@ | ||||||
| 	import ChevronUpDown from '../icons/ChevronUpDown.svelte'; | 	import ChevronUpDown from '../icons/ChevronUpDown.svelte'; | ||||||
| 	import Menu from './Navbar/Menu.svelte'; | 	import Menu from './Navbar/Menu.svelte'; | ||||||
| 	import TagChatModal from '../chat/TagChatModal.svelte'; | 	import TagChatModal from '../chat/TagChatModal.svelte'; | ||||||
|  | 	import { copyToClipboard } from '$lib/utils'; | ||||||
| 
 | 
 | ||||||
| 	const i18n = getContext('i18n'); | 	const i18n = getContext('i18n'); | ||||||
| 
 | 
 | ||||||
|  | @ -32,7 +33,7 @@ | ||||||
| 	export let addTag: Function; | 	export let addTag: Function; | ||||||
| 	export let deleteTag: Function; | 	export let deleteTag: Function; | ||||||
| 
 | 
 | ||||||
| 	export let showModelSelector = false; | 	export let showModelSelector = true; | ||||||
| 
 | 
 | ||||||
| 	let showShareChatModal = false; | 	let showShareChatModal = false; | ||||||
| 	let showTagChatModal = 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 downloadChat = async () => { | ||||||
| 		const chat = (await getChatById(localStorage.token, $chatId)).chat; | 		const chat = (await getChatById(localStorage.token, $chatId)).chat; | ||||||
| 		console.log('download', chat); | 		console.log('download', chat); | ||||||
|  | @ -80,7 +98,7 @@ | ||||||
| 	}; | 	}; | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <ShareChatModal bind:show={showShareChatModal} {downloadChat} {shareChat} /> | <ShareChatModal bind:show={showShareChatModal} {downloadChat} {shareChat} {shareLocalChat} /> | ||||||
| <!-- <TagChatModal bind:show={showTagChatModal} {tags} {deleteTag} {addTag} /> --> | <!-- <TagChatModal bind:show={showTagChatModal} {tags} {deleteTag} {addTag} /> --> | ||||||
| <nav id="nav" class=" sticky py-2.5 top-0 flex flex-row justify-center z-30"> | <nav id="nav" class=" sticky py-2.5 top-0 flex flex-row justify-center z-30"> | ||||||
| 	<div | 	<div | ||||||
|  | @ -135,8 +153,10 @@ | ||||||
| 		</div> --> | 		</div> --> | ||||||
| 
 | 
 | ||||||
| 		<div class="flex items-center w-full max-w-full"> | 		<div class="flex items-center w-full max-w-full"> | ||||||
| 			<div class="w-full flex-1 overflow-hidden max-w-full"> | 			<div class="flex-1 overflow-hidden max-w-full"> | ||||||
| 				<ModelSelector bind:selectedModels /> | 				{#if showModelSelector} | ||||||
|  | 					<ModelSelector bind:selectedModels /> | ||||||
|  | 				{/if} | ||||||
| 			</div> | 			</div> | ||||||
| 
 | 
 | ||||||
| 			<div class="self-start flex flex-none items-center"> | 			<div class="self-start flex flex-none items-center"> | ||||||
|  |  | ||||||
|  | @ -48,7 +48,7 @@ | ||||||
| 	let messagesContainerElement: HTMLDivElement; | 	let messagesContainerElement: HTMLDivElement; | ||||||
| 	let currentRequestId = null; | 	let currentRequestId = null; | ||||||
| 
 | 
 | ||||||
| 	let showModelSelector = false; | 	let showModelSelector = true; | ||||||
| 	let selectedModels = ['']; | 	let selectedModels = ['']; | ||||||
| 
 | 
 | ||||||
| 	let selectedModelfile = null; | 	let selectedModelfile = null; | ||||||
|  |  | ||||||
|  | @ -56,7 +56,7 @@ | ||||||
| 	let currentRequestId = null; | 	let currentRequestId = null; | ||||||
| 
 | 
 | ||||||
| 	// let chatId = $page.params.id; | 	// let chatId = $page.params.id; | ||||||
| 	let showModelSelector = false; | 	let showModelSelector = true; | ||||||
| 	let selectedModels = ['']; | 	let selectedModels = ['']; | ||||||
| 	let selectedModelfile = null; | 	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