forked from open-webui/open-webui
		
	Merge branch 'dev' into feat/add-i18n
This commit is contained in:
		
						commit
						7031aa14e8
					
				
					 30 changed files with 849 additions and 569 deletions
				
			
		|  | @ -43,6 +43,10 @@ ol > li { | |||
| 	font-weight: 400; | ||||
| } | ||||
| 
 | ||||
| li p { | ||||
| 	display: inline; | ||||
| } | ||||
| 
 | ||||
| ::-webkit-scrollbar-thumb { | ||||
| 	--tw-border-opacity: 1; | ||||
| 	background-color: rgba(217, 217, 227, 0.8); | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| import { OPENAI_API_BASE_URL } from '$lib/constants'; | ||||
| 
 | ||||
| export const getOpenAIUrl = async (token: string = '') => { | ||||
| export const getOpenAIUrls = async (token: string = '') => { | ||||
| 	let error = null; | ||||
| 
 | ||||
| 	const res = await fetch(`${OPENAI_API_BASE_URL}/url`, { | ||||
| 	const res = await fetch(`${OPENAI_API_BASE_URL}/urls`, { | ||||
| 		method: 'GET', | ||||
| 		headers: { | ||||
| 			Accept: 'application/json', | ||||
|  | @ -29,13 +29,13 @@ export const getOpenAIUrl = async (token: string = '') => { | |||
| 		throw error; | ||||
| 	} | ||||
| 
 | ||||
| 	return res.OPENAI_API_BASE_URL; | ||||
| 	return res.OPENAI_API_BASE_URLS; | ||||
| }; | ||||
| 
 | ||||
| export const updateOpenAIUrl = async (token: string = '', url: string) => { | ||||
| export const updateOpenAIUrls = async (token: string = '', urls: string[]) => { | ||||
| 	let error = null; | ||||
| 
 | ||||
| 	const res = await fetch(`${OPENAI_API_BASE_URL}/url/update`, { | ||||
| 	const res = await fetch(`${OPENAI_API_BASE_URL}/urls/update`, { | ||||
| 		method: 'POST', | ||||
| 		headers: { | ||||
| 			Accept: 'application/json', | ||||
|  | @ -43,7 +43,7 @@ export const updateOpenAIUrl = async (token: string = '', url: string) => { | |||
| 			...(token && { authorization: `Bearer ${token}` }) | ||||
| 		}, | ||||
| 		body: JSON.stringify({ | ||||
| 			url: url | ||||
| 			urls: urls | ||||
| 		}) | ||||
| 	}) | ||||
| 		.then(async (res) => { | ||||
|  | @ -64,13 +64,13 @@ export const updateOpenAIUrl = async (token: string = '', url: string) => { | |||
| 		throw error; | ||||
| 	} | ||||
| 
 | ||||
| 	return res.OPENAI_API_BASE_URL; | ||||
| 	return res.OPENAI_API_BASE_URLS; | ||||
| }; | ||||
| 
 | ||||
| export const getOpenAIKey = async (token: string = '') => { | ||||
| export const getOpenAIKeys = async (token: string = '') => { | ||||
| 	let error = null; | ||||
| 
 | ||||
| 	const res = await fetch(`${OPENAI_API_BASE_URL}/key`, { | ||||
| 	const res = await fetch(`${OPENAI_API_BASE_URL}/keys`, { | ||||
| 		method: 'GET', | ||||
| 		headers: { | ||||
| 			Accept: 'application/json', | ||||
|  | @ -96,13 +96,13 @@ export const getOpenAIKey = async (token: string = '') => { | |||
| 		throw error; | ||||
| 	} | ||||
| 
 | ||||
| 	return res.OPENAI_API_KEY; | ||||
| 	return res.OPENAI_API_KEYS; | ||||
| }; | ||||
| 
 | ||||
| export const updateOpenAIKey = async (token: string = '', key: string) => { | ||||
| export const updateOpenAIKeys = async (token: string = '', keys: string[]) => { | ||||
| 	let error = null; | ||||
| 
 | ||||
| 	const res = await fetch(`${OPENAI_API_BASE_URL}/key/update`, { | ||||
| 	const res = await fetch(`${OPENAI_API_BASE_URL}/keys/update`, { | ||||
| 		method: 'POST', | ||||
| 		headers: { | ||||
| 			Accept: 'application/json', | ||||
|  | @ -110,7 +110,7 @@ export const updateOpenAIKey = async (token: string = '', key: string) => { | |||
| 			...(token && { authorization: `Bearer ${token}` }) | ||||
| 		}, | ||||
| 		body: JSON.stringify({ | ||||
| 			key: key | ||||
| 			keys: keys | ||||
| 		}) | ||||
| 	}) | ||||
| 		.then(async (res) => { | ||||
|  | @ -131,7 +131,7 @@ export const updateOpenAIKey = async (token: string = '', key: string) => { | |||
| 		throw error; | ||||
| 	} | ||||
| 
 | ||||
| 	return res.OPENAI_API_KEY; | ||||
| 	return res.OPENAI_API_KEYS; | ||||
| }; | ||||
| 
 | ||||
| export const getOpenAIModels = async (token: string = '') => { | ||||
|  |  | |||
|  | @ -225,33 +225,80 @@ | |||
| 		}, 100); | ||||
| 	}; | ||||
| 
 | ||||
| 	// TODO: change delete behaviour | ||||
| 	// const deleteMessageAndDescendants = async (messageId: string) => { | ||||
| 	// 	if (history.messages[messageId]) { | ||||
| 	// 		history.messages[messageId].deleted = true; | ||||
| 
 | ||||
| 	// 		for (const childId of history.messages[messageId].childrenIds) { | ||||
| 	// 			await deleteMessageAndDescendants(childId); | ||||
| 	// 		} | ||||
| 	// 	} | ||||
| 	// }; | ||||
| 
 | ||||
| 	// const triggerDeleteMessageRecursive = async (messageId: string) => { | ||||
| 	// 	await deleteMessageAndDescendants(messageId); | ||||
| 	// 	await updateChatById(localStorage.token, chatId, { history }); | ||||
| 	// 	await chats.set(await getChatList(localStorage.token)); | ||||
| 	// }; | ||||
| 
 | ||||
| 	const messageDeleteHandler = async (messageId) => { | ||||
| 		if (history.messages[messageId]) { | ||||
| 			history.messages[messageId].deleted = true; | ||||
| 
 | ||||
| 			for (const childId of history.messages[messageId].childrenIds) { | ||||
| 				history.messages[childId].deleted = true; | ||||
| 		const messageToDelete = history.messages[messageId]; | ||||
| 		const messageParentId = messageToDelete.parentId; | ||||
| 		const messageChildrenIds = messageToDelete.childrenIds ?? []; | ||||
| 		const hasSibling = messageChildrenIds.some( | ||||
| 			(childId) => history.messages[childId]?.childrenIds?.length > 0 | ||||
| 		); | ||||
| 		messageChildrenIds.forEach((childId) => { | ||||
| 			const child = history.messages[childId]; | ||||
| 			if (child && child.childrenIds) { | ||||
| 				if (child.childrenIds.length === 0 && !hasSibling) { | ||||
| 					// if last prompt/response pair | ||||
| 					history.messages[messageParentId].childrenIds = []; | ||||
| 					history.currentId = messageParentId; | ||||
| 				} else { | ||||
| 					child.childrenIds.forEach((grandChildId) => { | ||||
| 						if (history.messages[grandChildId]) { | ||||
| 							history.messages[grandChildId].parentId = messageParentId; | ||||
| 							history.messages[messageParentId].childrenIds.push(grandChildId); | ||||
| 						} | ||||
| 					}); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		await updateChatById(localStorage.token, chatId, { history }); | ||||
| 			// remove response | ||||
| 			history.messages[messageParentId].childrenIds = history.messages[ | ||||
| 				messageParentId | ||||
| 			].childrenIds.filter((id) => id !== childId); | ||||
| 		}); | ||||
| 		// remove prompt | ||||
| 		history.messages[messageParentId].childrenIds = history.messages[ | ||||
| 			messageParentId | ||||
| 		].childrenIds.filter((id) => id !== messageId); | ||||
| 		await updateChatById(localStorage.token, chatId, { | ||||
| 			messages: messages, | ||||
| 			history: history | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| 	// const messageDeleteHandler = async (messageId) => { | ||||
| 	// 	const message = history.messages[messageId]; | ||||
| 	// 	const parentId = message.parentId; | ||||
| 	// 	const childrenIds = message.childrenIds ?? []; | ||||
| 	// 	const grandchildrenIds = []; | ||||
| 
 | ||||
| 	// 	// Iterate through childrenIds to find grandchildrenIds | ||||
| 	// 	for (const childId of childrenIds) { | ||||
| 	// 		const childMessage = history.messages[childId]; | ||||
| 	// 		const grandChildrenIds = childMessage.childrenIds ?? []; | ||||
| 
 | ||||
| 	// 		for (const grandchildId of grandchildrenIds) { | ||||
| 	// 			const childMessage = history.messages[grandchildId]; | ||||
| 	// 			childMessage.parentId = parentId; | ||||
| 	// 		} | ||||
| 	// 		grandchildrenIds.push(...grandChildrenIds); | ||||
| 	// 	} | ||||
| 
 | ||||
| 	// 	history.messages[parentId].childrenIds.push(...grandchildrenIds); | ||||
| 	// 	history.messages[parentId].childrenIds = history.messages[parentId].childrenIds.filter( | ||||
| 	// 		(id) => id !== messageId | ||||
| 	// 	); | ||||
| 
 | ||||
| 	// 	// Select latest message | ||||
| 	// 	let currentMessageId = grandchildrenIds.at(-1); | ||||
| 	// 	if (currentMessageId) { | ||||
| 	// 		let messageChildrenIds = history.messages[currentMessageId].childrenIds; | ||||
| 	// 		while (messageChildrenIds.length !== 0) { | ||||
| 	// 			currentMessageId = messageChildrenIds.at(-1); | ||||
| 	// 			messageChildrenIds = history.messages[currentMessageId].childrenIds; | ||||
| 	// 		} | ||||
| 	// 		history.currentId = currentMessageId; | ||||
| 	// 	} | ||||
| 
 | ||||
| 	// 	await updateChatById(localStorage.token, chatId, { messages, history }); | ||||
| 	// }; | ||||
| </script> | ||||
| 
 | ||||
| {#if messages.length == 0} | ||||
|  | @ -260,57 +307,55 @@ | |||
| 	<div class=" pb-10"> | ||||
| 		{#key chatId} | ||||
| 			{#each messages as message, messageIdx} | ||||
| 				{#if !message.deleted} | ||||
| 					<div class=" w-full"> | ||||
| 						<div | ||||
| 							class="flex flex-col justify-between px-5 mb-3 {$settings?.fullScreenMode ?? null | ||||
| 								? 'max-w-full' | ||||
| 								: 'max-w-3xl'} mx-auto rounded-lg group" | ||||
| 						> | ||||
| 							{#if message.role === 'user'} | ||||
| 								<UserMessage | ||||
| 									on:delete={() => messageDeleteHandler(message.id)} | ||||
| 									user={$user} | ||||
| 									{message} | ||||
| 									isFirstMessage={messageIdx === 0} | ||||
| 									siblings={message.parentId !== null | ||||
| 										? history.messages[message.parentId]?.childrenIds ?? [] | ||||
| 										: Object.values(history.messages) | ||||
| 												.filter((message) => message.parentId === null) | ||||
| 												.map((message) => message.id) ?? []} | ||||
| 									{confirmEditMessage} | ||||
| 									{showPreviousMessage} | ||||
| 									{showNextMessage} | ||||
| 									{copyToClipboard} | ||||
| 								/> | ||||
| 							{:else} | ||||
| 								<ResponseMessage | ||||
| 									{message} | ||||
| 									modelfiles={selectedModelfiles} | ||||
| 									siblings={history.messages[message.parentId]?.childrenIds ?? []} | ||||
| 									isLastMessage={messageIdx + 1 === messages.length} | ||||
| 									{confirmEditResponseMessage} | ||||
| 									{showPreviousMessage} | ||||
| 									{showNextMessage} | ||||
| 									{rateMessage} | ||||
| 									{copyToClipboard} | ||||
| 									{continueGeneration} | ||||
| 									{regenerateResponse} | ||||
| 									on:save={async (e) => { | ||||
| 										console.log('save', e); | ||||
| 				<div class=" w-full"> | ||||
| 					<div | ||||
| 						class="flex flex-col justify-between px-5 mb-3 {$settings?.fullScreenMode ?? null | ||||
| 							? 'max-w-full' | ||||
| 							: 'max-w-3xl'} mx-auto rounded-lg group" | ||||
| 					> | ||||
| 						{#if message.role === 'user'} | ||||
| 							<UserMessage | ||||
| 								on:delete={() => messageDeleteHandler(message.id)} | ||||
| 								user={$user} | ||||
| 								{message} | ||||
| 								isFirstMessage={messageIdx === 0} | ||||
| 								siblings={message.parentId !== null | ||||
| 									? history.messages[message.parentId]?.childrenIds ?? [] | ||||
| 									: Object.values(history.messages) | ||||
| 											.filter((message) => message.parentId === null) | ||||
| 											.map((message) => message.id) ?? []} | ||||
| 								{confirmEditMessage} | ||||
| 								{showPreviousMessage} | ||||
| 								{showNextMessage} | ||||
| 								{copyToClipboard} | ||||
| 							/> | ||||
| 						{:else} | ||||
| 							<ResponseMessage | ||||
| 								{message} | ||||
| 								modelfiles={selectedModelfiles} | ||||
| 								siblings={history.messages[message.parentId]?.childrenIds ?? []} | ||||
| 								isLastMessage={messageIdx + 1 === messages.length} | ||||
| 								{confirmEditResponseMessage} | ||||
| 								{showPreviousMessage} | ||||
| 								{showNextMessage} | ||||
| 								{rateMessage} | ||||
| 								{copyToClipboard} | ||||
| 								{continueGeneration} | ||||
| 								{regenerateResponse} | ||||
| 								on:save={async (e) => { | ||||
| 									console.log('save', e); | ||||
| 
 | ||||
| 										const message = e.detail; | ||||
| 										history.messages[message.id] = message; | ||||
| 										await updateChatById(localStorage.token, chatId, { | ||||
| 											messages: messages, | ||||
| 											history: history | ||||
| 										}); | ||||
| 									}} | ||||
| 								/> | ||||
| 							{/if} | ||||
| 						</div> | ||||
| 									const message = e.detail; | ||||
| 									history.messages[message.id] = message; | ||||
| 									await updateChatById(localStorage.token, chatId, { | ||||
| 										messages: messages, | ||||
| 										history: history | ||||
| 									}); | ||||
| 								}} | ||||
| 							/> | ||||
| 						{/if} | ||||
| 					</div> | ||||
| 				{/if} | ||||
| 				</div> | ||||
| 			{/each} | ||||
| 
 | ||||
| 			{#if bottomPadding} | ||||
|  |  | |||
|  | @ -24,6 +24,7 @@ | |||
| 	import CodeBlock from './CodeBlock.svelte'; | ||||
| 	import Image from '$lib/components/common/Image.svelte'; | ||||
| 	import { WEBUI_BASE_URL } from '$lib/constants'; | ||||
| 	import Tooltip from '$lib/components/common/Tooltip.svelte'; | ||||
| 
 | ||||
| 	export let modelfiles = []; | ||||
| 	export let message; | ||||
|  | @ -346,6 +347,7 @@ | |||
| 									class=" bg-transparent outline-none w-full resize-none" | ||||
| 									bind:value={editedContent} | ||||
| 									on:input={(e) => { | ||||
| 										e.target.style.height = ''; | ||||
| 										e.target.style.height = `${e.target.scrollHeight}px`; | ||||
| 									}} | ||||
| 								/> | ||||
|  | @ -464,189 +466,125 @@ | |||
| 											</div> | ||||
| 										{/if} | ||||
| 
 | ||||
| 										<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="1.5" | ||||
| 												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> | ||||
| 
 | ||||
| 										<button | ||||
| 											class="{isLastMessage | ||||
| 												? 'visible' | ||||
| 												: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition copy-response-button" | ||||
| 											on:click={() => { | ||||
| 												copyToClipboard(message.content); | ||||
| 											}} | ||||
| 										> | ||||
| 											<svg | ||||
| 												xmlns="http://www.w3.org/2000/svg" | ||||
| 												fill="none" | ||||
| 												viewBox="0 0 24 24" | ||||
| 												stroke-width="1.5" | ||||
| 												stroke="currentColor" | ||||
| 												class="w-4 h-4" | ||||
| 											> | ||||
| 												<path | ||||
| 													stroke-linecap="round" | ||||
| 													stroke-linejoin="round" | ||||
| 													d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" | ||||
| 												/> | ||||
| 											</svg> | ||||
| 										</button> | ||||
| 
 | ||||
| 										<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 | ||||
| 											> | ||||
| 										</button> | ||||
| 										<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 | ||||
| 											> | ||||
| 										</button> | ||||
| 
 | ||||
| 										<button | ||||
| 											id="speak-button-{message.id}" | ||||
| 											class="{isLastMessage | ||||
| 												? 'visible' | ||||
| 												: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition" | ||||
| 											on:click={() => { | ||||
| 												if (!loadingSpeech) { | ||||
| 													toggleSpeakMessage(message); | ||||
| 												} | ||||
| 											}} | ||||
| 										> | ||||
| 											{#if loadingSpeech} | ||||
| 												<svg | ||||
| 													class=" w-4 h-4" | ||||
| 													fill="currentColor" | ||||
| 													viewBox="0 0 24 24" | ||||
| 													xmlns="http://www.w3.org/2000/svg" | ||||
| 													><style> | ||||
| 														.spinner_S1WN { | ||||
| 															animation: spinner_MGfb 0.8s linear infinite; | ||||
| 															animation-delay: -0.8s; | ||||
| 														} | ||||
| 														.spinner_Km9P { | ||||
| 															animation-delay: -0.65s; | ||||
| 														} | ||||
| 														.spinner_JApP { | ||||
| 															animation-delay: -0.5s; | ||||
| 														} | ||||
| 														@keyframes spinner_MGfb { | ||||
| 															93.75%, | ||||
| 															100% { | ||||
| 																opacity: 0.2; | ||||
| 															} | ||||
| 														} | ||||
| 													</style><circle class="spinner_S1WN" cx="4" cy="12" r="3" /><circle | ||||
| 														class="spinner_S1WN spinner_Km9P" | ||||
| 														cx="12" | ||||
| 														cy="12" | ||||
| 														r="3" | ||||
| 													/><circle class="spinner_S1WN spinner_JApP" cx="20" cy="12" r="3" /></svg | ||||
| 												> | ||||
| 											{:else if speaking} | ||||
| 												<svg | ||||
| 													xmlns="http://www.w3.org/2000/svg" | ||||
| 													fill="none" | ||||
| 													viewBox="0 0 24 24" | ||||
| 													stroke-width="1.5" | ||||
| 													stroke="currentColor" | ||||
| 													class="w-4 h-4" | ||||
| 												> | ||||
| 													<path | ||||
| 														stroke-linecap="round" | ||||
| 														stroke-linejoin="round" | ||||
| 														d="M17.25 9.75 19.5 12m0 0 2.25 2.25M19.5 12l2.25-2.25M19.5 12l-2.25 2.25m-10.5-6 4.72-4.72a.75.75 0 0 1 1.28.53v15.88a.75.75 0 0 1-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.009 9.009 0 0 1 2.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75Z" | ||||
| 													/> | ||||
| 												</svg> | ||||
| 											{:else} | ||||
| 												<svg | ||||
| 													xmlns="http://www.w3.org/2000/svg" | ||||
| 													fill="none" | ||||
| 													viewBox="0 0 24 24" | ||||
| 													stroke-width="1.5" | ||||
| 													stroke="currentColor" | ||||
| 													class="w-4 h-4" | ||||
| 												> | ||||
| 													<path | ||||
| 														stroke-linecap="round" | ||||
| 														stroke-linejoin="round" | ||||
| 														d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z" | ||||
| 													/> | ||||
| 												</svg> | ||||
| 											{/if} | ||||
| 										</button> | ||||
| 
 | ||||
| 										{#if $config.images} | ||||
| 										<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={() => { | ||||
| 													if (!generatingImage) { | ||||
| 														generateImage(message); | ||||
| 													editMessageHandler(); | ||||
| 												}} | ||||
| 											> | ||||
| 												<svg | ||||
| 													xmlns="http://www.w3.org/2000/svg" | ||||
| 													fill="none" | ||||
| 													viewBox="0 0 24 24" | ||||
| 													stroke-width="1.5" | ||||
| 													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> | ||||
| 
 | ||||
| 										<Tooltip content="Copy" placement="bottom"> | ||||
| 											<button | ||||
| 												class="{isLastMessage | ||||
| 													? 'visible' | ||||
| 													: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition copy-response-button" | ||||
| 												on:click={() => { | ||||
| 													copyToClipboard(message.content); | ||||
| 												}} | ||||
| 											> | ||||
| 												<svg | ||||
| 													xmlns="http://www.w3.org/2000/svg" | ||||
| 													fill="none" | ||||
| 													viewBox="0 0 24 24" | ||||
| 													stroke-width="1.5" | ||||
| 													stroke="currentColor" | ||||
| 													class="w-4 h-4" | ||||
| 												> | ||||
| 													<path | ||||
| 														stroke-linecap="round" | ||||
| 														stroke-linejoin="round" | ||||
| 														d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" | ||||
| 													/> | ||||
| 												</svg> | ||||
| 											</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 | ||||
| 												> | ||||
| 											</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 | ||||
| 												> | ||||
| 											</button> | ||||
| 										</Tooltip> | ||||
| 
 | ||||
| 										<Tooltip content="Read Aloud" placement="bottom"> | ||||
| 											<button | ||||
| 												id="speak-button-{message.id}" | ||||
| 												class="{isLastMessage | ||||
| 													? 'visible' | ||||
| 													: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition" | ||||
| 												on:click={() => { | ||||
| 													if (!loadingSpeech) { | ||||
| 														toggleSpeakMessage(message); | ||||
| 													} | ||||
| 												}} | ||||
| 											> | ||||
| 												{#if generatingImage} | ||||
| 												{#if loadingSpeech} | ||||
| 													<svg | ||||
| 														class=" w-4 h-4" | ||||
| 														fill="currentColor" | ||||
|  | @ -681,6 +619,21 @@ | |||
| 															r="3" | ||||
| 														/></svg | ||||
| 													> | ||||
| 												{:else if speaking} | ||||
| 													<svg | ||||
| 														xmlns="http://www.w3.org/2000/svg" | ||||
| 														fill="none" | ||||
| 														viewBox="0 0 24 24" | ||||
| 														stroke-width="1.5" | ||||
| 														stroke="currentColor" | ||||
| 														class="w-4 h-4" | ||||
| 													> | ||||
| 														<path | ||||
| 															stroke-linecap="round" | ||||
| 															stroke-linejoin="round" | ||||
| 															d="M17.25 9.75 19.5 12m0 0 2.25 2.25M19.5 12l2.25-2.25M19.5 12l-2.25 2.25m-10.5-6 4.72-4.72a.75.75 0 0 1 1.28.53v15.88a.75.75 0 0 1-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.009 9.009 0 0 1 2.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75Z" | ||||
| 														/> | ||||
| 													</svg> | ||||
| 												{:else} | ||||
| 													<svg | ||||
| 														xmlns="http://www.w3.org/2000/svg" | ||||
|  | @ -693,93 +646,166 @@ | |||
| 														<path | ||||
| 															stroke-linecap="round" | ||||
| 															stroke-linejoin="round" | ||||
| 															d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" | ||||
| 															d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z" | ||||
| 														/> | ||||
| 													</svg> | ||||
| 												{/if} | ||||
| 											</button> | ||||
| 										</Tooltip> | ||||
| 
 | ||||
| 										{#if $config.images} | ||||
| 											<Tooltip content="Generate Image" placement="bottom"> | ||||
| 												<button | ||||
| 													class="{isLastMessage | ||||
| 														? 'visible' | ||||
| 														: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition" | ||||
| 													on:click={() => { | ||||
| 														if (!generatingImage) { | ||||
| 															generateImage(message); | ||||
| 														} | ||||
| 													}} | ||||
| 												> | ||||
| 													{#if generatingImage} | ||||
| 														<svg | ||||
| 															class=" w-4 h-4" | ||||
| 															fill="currentColor" | ||||
| 															viewBox="0 0 24 24" | ||||
| 															xmlns="http://www.w3.org/2000/svg" | ||||
| 															><style> | ||||
| 																.spinner_S1WN { | ||||
| 																	animation: spinner_MGfb 0.8s linear infinite; | ||||
| 																	animation-delay: -0.8s; | ||||
| 																} | ||||
| 																.spinner_Km9P { | ||||
| 																	animation-delay: -0.65s; | ||||
| 																} | ||||
| 																.spinner_JApP { | ||||
| 																	animation-delay: -0.5s; | ||||
| 																} | ||||
| 																@keyframes spinner_MGfb { | ||||
| 																	93.75%, | ||||
| 																	100% { | ||||
| 																		opacity: 0.2; | ||||
| 																	} | ||||
| 																} | ||||
| 															</style><circle class="spinner_S1WN" cx="4" cy="12" r="3" /><circle | ||||
| 																class="spinner_S1WN spinner_Km9P" | ||||
| 																cx="12" | ||||
| 																cy="12" | ||||
| 																r="3" | ||||
| 															/><circle | ||||
| 																class="spinner_S1WN spinner_JApP" | ||||
| 																cx="20" | ||||
| 																cy="12" | ||||
| 																r="3" | ||||
| 															/></svg | ||||
| 														> | ||||
| 													{:else} | ||||
| 														<svg | ||||
| 															xmlns="http://www.w3.org/2000/svg" | ||||
| 															fill="none" | ||||
| 															viewBox="0 0 24 24" | ||||
| 															stroke-width="1.5" | ||||
| 															stroke="currentColor" | ||||
| 															class="w-4 h-4" | ||||
| 														> | ||||
| 															<path | ||||
| 																stroke-linecap="round" | ||||
| 																stroke-linejoin="round" | ||||
| 																d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" | ||||
| 															/> | ||||
| 														</svg> | ||||
| 													{/if} | ||||
| 												</button> | ||||
| 											</Tooltip> | ||||
| 										{/if} | ||||
| 
 | ||||
| 										{#if message.info} | ||||
| 											<button | ||||
| 												class=" {isLastMessage | ||||
| 													? 'visible' | ||||
| 													: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition whitespace-pre-wrap" | ||||
| 												on:click={() => { | ||||
| 													console.log(message); | ||||
| 												}} | ||||
| 												id="info-{message.id}" | ||||
| 											> | ||||
| 												<svg | ||||
| 													xmlns="http://www.w3.org/2000/svg" | ||||
| 													fill="none" | ||||
| 													viewBox="0 0 24 24" | ||||
| 													stroke-width="1.5" | ||||
| 													stroke="currentColor" | ||||
| 													class="w-4 h-4" | ||||
| 											<Tooltip content="Generation Info" placement="bottom"> | ||||
| 												<button | ||||
| 													class=" {isLastMessage | ||||
| 														? 'visible' | ||||
| 														: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition whitespace-pre-wrap" | ||||
| 													on:click={() => { | ||||
| 														console.log(message); | ||||
| 													}} | ||||
| 													id="info-{message.id}" | ||||
| 												> | ||||
| 													<path | ||||
| 														stroke-linecap="round" | ||||
| 														stroke-linejoin="round" | ||||
| 														d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" | ||||
| 													/> | ||||
| 												</svg> | ||||
| 											</button> | ||||
| 													<svg | ||||
| 														xmlns="http://www.w3.org/2000/svg" | ||||
| 														fill="none" | ||||
| 														viewBox="0 0 24 24" | ||||
| 														stroke-width="1.5" | ||||
| 														stroke="currentColor" | ||||
| 														class="w-4 h-4" | ||||
| 													> | ||||
| 														<path | ||||
| 															stroke-linecap="round" | ||||
| 															stroke-linejoin="round" | ||||
| 															d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" | ||||
| 														/> | ||||
| 													</svg> | ||||
| 												</button> | ||||
| 											</Tooltip> | ||||
| 										{/if} | ||||
| 
 | ||||
| 										{#if isLastMessage} | ||||
| 											<button | ||||
| 												type="button" | ||||
| 												class="{isLastMessage | ||||
| 													? 'visible' | ||||
| 													: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition regenerate-response-button" | ||||
| 												on:click={() => { | ||||
| 													continueGeneration(); | ||||
| 												}} | ||||
| 											> | ||||
| 												<svg | ||||
| 													xmlns="http://www.w3.org/2000/svg" | ||||
| 													fill="none" | ||||
| 													viewBox="0 0 24 24" | ||||
| 													stroke-width="1.5" | ||||
| 													stroke="currentColor" | ||||
| 													class="w-4 h-4" | ||||
| 											<Tooltip content="Continue Response" placement="bottom"> | ||||
| 												<button | ||||
| 													type="button" | ||||
| 													class="{isLastMessage | ||||
| 														? 'visible' | ||||
| 														: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition regenerate-response-button" | ||||
| 													on:click={() => { | ||||
| 														continueGeneration(); | ||||
| 													}} | ||||
| 												> | ||||
| 													<path | ||||
| 														stroke-linecap="round" | ||||
| 														stroke-linejoin="round" | ||||
| 														d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" | ||||
| 													/> | ||||
| 													<path | ||||
| 														stroke-linecap="round" | ||||
| 														stroke-linejoin="round" | ||||
| 														d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z" | ||||
| 													/> | ||||
| 												</svg> | ||||
| 											</button> | ||||
| 													<svg | ||||
| 														xmlns="http://www.w3.org/2000/svg" | ||||
| 														fill="none" | ||||
| 														viewBox="0 0 24 24" | ||||
| 														stroke-width="1.5" | ||||
| 														stroke="currentColor" | ||||
| 														class="w-4 h-4" | ||||
| 													> | ||||
| 														<path | ||||
| 															stroke-linecap="round" | ||||
| 															stroke-linejoin="round" | ||||
| 															d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" | ||||
| 														/> | ||||
| 														<path | ||||
| 															stroke-linecap="round" | ||||
| 															stroke-linejoin="round" | ||||
| 															d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z" | ||||
| 														/> | ||||
| 													</svg> | ||||
| 												</button> | ||||
| 											</Tooltip> | ||||
| 
 | ||||
| 											<button | ||||
| 												type="button" | ||||
| 												class="{isLastMessage | ||||
| 													? 'visible' | ||||
| 													: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition regenerate-response-button" | ||||
| 												on:click={regenerateResponse} | ||||
| 											> | ||||
| 												<svg | ||||
| 													xmlns="http://www.w3.org/2000/svg" | ||||
| 													fill="none" | ||||
| 													viewBox="0 0 24 24" | ||||
| 													stroke-width="1.5" | ||||
| 													stroke="currentColor" | ||||
| 													class="w-4 h-4" | ||||
| 											<Tooltip content="Regenerate" placement="bottom"> | ||||
| 												<button | ||||
| 													type="button" | ||||
| 													class="{isLastMessage | ||||
| 														? 'visible' | ||||
| 														: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition regenerate-response-button" | ||||
| 													on:click={regenerateResponse} | ||||
| 												> | ||||
| 													<path | ||||
| 														stroke-linecap="round" | ||||
| 														stroke-linejoin="round" | ||||
| 														d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" | ||||
| 													/> | ||||
| 												</svg> | ||||
| 											</button> | ||||
| 													<svg | ||||
| 														xmlns="http://www.w3.org/2000/svg" | ||||
| 														fill="none" | ||||
| 														viewBox="0 0 24 24" | ||||
| 														stroke-width="1.5" | ||||
| 														stroke="currentColor" | ||||
| 														class="w-4 h-4" | ||||
| 													> | ||||
| 														<path | ||||
| 															stroke-linecap="round" | ||||
| 															stroke-linejoin="round" | ||||
| 															d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" | ||||
| 														/> | ||||
| 													</svg> | ||||
| 												</button> | ||||
| 											</Tooltip> | ||||
| 										{/if} | ||||
| 									</div> | ||||
| 								{/if} | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| 	import Name from './Name.svelte'; | ||||
| 	import ProfileImage from './ProfileImage.svelte'; | ||||
| 	import { modelfiles, settings } from '$lib/stores'; | ||||
| 	import Tooltip from '$lib/components/common/Tooltip.svelte'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
|  | @ -171,7 +172,8 @@ | |||
| 						class=" bg-transparent outline-none w-full resize-none" | ||||
| 						bind:value={editedContent} | ||||
| 						on:input={(e) => { | ||||
| 							messageEditTextAreaElement.style.height = `${messageEditTextAreaElement.scrollHeight}px`; | ||||
| 							e.target.style.height = ''; | ||||
| 							e.target.style.height = `${e.target.scrollHeight}px`; | ||||
| 						}} | ||||
| 					/> | ||||
| 
 | ||||
|  | @ -248,55 +250,11 @@ | |||
| 							</div> | ||||
| 						{/if} | ||||
| 
 | ||||
| 						<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="1.5" | ||||
| 								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> | ||||
| 
 | ||||
| 						<button | ||||
| 							class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition" | ||||
| 							on:click={() => { | ||||
| 								copyToClipboard(message.content); | ||||
| 							}} | ||||
| 						> | ||||
| 							<svg | ||||
| 								xmlns="http://www.w3.org/2000/svg" | ||||
| 								fill="none" | ||||
| 								viewBox="0 0 24 24" | ||||
| 								stroke-width="1.5" | ||||
| 								stroke="currentColor" | ||||
| 								class="w-4 h-4" | ||||
| 							> | ||||
| 								<path | ||||
| 									stroke-linecap="round" | ||||
| 									stroke-linejoin="round" | ||||
| 									d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" | ||||
| 								/> | ||||
| 							</svg> | ||||
| 						</button> | ||||
| 
 | ||||
| 						{#if !isFirstMessage} | ||||
| 						<Tooltip content="Edit" placement="bottom"> | ||||
| 							<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 edit-user-message-button" | ||||
| 								on:click={() => { | ||||
| 									deleteMessageHandler(); | ||||
| 									editMessageHandler(); | ||||
| 								}} | ||||
| 							> | ||||
| 								<svg | ||||
|  | @ -310,10 +268,60 @@ | |||
| 									<path | ||||
| 										stroke-linecap="round" | ||||
| 										stroke-linejoin="round" | ||||
| 										d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" | ||||
| 										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> | ||||
| 
 | ||||
| 						<Tooltip content="Copy" placement="bottom"> | ||||
| 							<button | ||||
| 								class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition" | ||||
| 								on:click={() => { | ||||
| 									copyToClipboard(message.content); | ||||
| 								}} | ||||
| 							> | ||||
| 								<svg | ||||
| 									xmlns="http://www.w3.org/2000/svg" | ||||
| 									fill="none" | ||||
| 									viewBox="0 0 24 24" | ||||
| 									stroke-width="1.5" | ||||
| 									stroke="currentColor" | ||||
| 									class="w-4 h-4" | ||||
| 								> | ||||
| 									<path | ||||
| 										stroke-linecap="round" | ||||
| 										stroke-linejoin="round" | ||||
| 										d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" | ||||
| 									/> | ||||
| 								</svg> | ||||
| 							</button> | ||||
| 						</Tooltip> | ||||
| 
 | ||||
| 						{#if !isFirstMessage} | ||||
| 							<Tooltip content="Delete" placement="bottom"> | ||||
| 								<button | ||||
| 									class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition" | ||||
| 									on:click={() => { | ||||
| 										deleteMessageHandler(); | ||||
| 									}} | ||||
| 								> | ||||
| 									<svg | ||||
| 										xmlns="http://www.w3.org/2000/svg" | ||||
| 										fill="none" | ||||
| 										viewBox="0 0 24 24" | ||||
| 										stroke-width="1.5" | ||||
| 										stroke="currentColor" | ||||
| 										class="w-4 h-4" | ||||
| 									> | ||||
| 										<path | ||||
| 											stroke-linecap="round" | ||||
| 											stroke-linejoin="round" | ||||
| 											d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" | ||||
| 										/> | ||||
| 									</svg> | ||||
| 								</button> | ||||
| 							</Tooltip> | ||||
| 						{/if} | ||||
| 					</div> | ||||
| 				</div> | ||||
|  |  | |||
|  | @ -4,7 +4,12 @@ | |||
| 	const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
| 	import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama'; | ||||
| 	import { getOpenAIKey, getOpenAIUrl, updateOpenAIKey, updateOpenAIUrl } from '$lib/apis/openai'; | ||||
| 	import { | ||||
| 		getOpenAIKeys, | ||||
| 		getOpenAIUrls, | ||||
| 		updateOpenAIKeys, | ||||
| 		updateOpenAIUrls | ||||
| 	} from '$lib/apis/openai'; | ||||
| 	import { toast } from 'svelte-sonner'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
|  | @ -18,12 +23,14 @@ | |||
| 	let OPENAI_API_KEY = ''; | ||||
| 	let OPENAI_API_BASE_URL = ''; | ||||
| 
 | ||||
| 	let OPENAI_API_KEYS = ['']; | ||||
| 	let OPENAI_API_BASE_URLS = ['']; | ||||
| 
 | ||||
| 	let showOpenAI = false; | ||||
| 	let showLiteLLM = false; | ||||
| 
 | ||||
| 	const updateOpenAIHandler = async () => { | ||||
| 		OPENAI_API_BASE_URL = await updateOpenAIUrl(localStorage.token, OPENAI_API_BASE_URL); | ||||
| 		OPENAI_API_KEY = await updateOpenAIKey(localStorage.token, OPENAI_API_KEY); | ||||
| 		OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS); | ||||
| 		OPENAI_API_KEYS = await updateOpenAIKeys(localStorage.token, OPENAI_API_KEYS); | ||||
| 
 | ||||
| 		await models.set(await getModels()); | ||||
| 	}; | ||||
|  | @ -45,8 +52,8 @@ | |||
| 	onMount(async () => { | ||||
| 		if ($user.role === 'admin') { | ||||
| 			OLLAMA_BASE_URLS = await getOllamaUrls(localStorage.token); | ||||
| 			OPENAI_API_BASE_URL = await getOpenAIUrl(localStorage.token); | ||||
| 			OPENAI_API_KEY = await getOpenAIKey(localStorage.token); | ||||
| 			OPENAI_API_BASE_URLS = await getOpenAIUrls(localStorage.token); | ||||
| 			OPENAI_API_KEYS = await getOpenAIKeys(localStorage.token); | ||||
| 		} | ||||
| 	}); | ||||
| </script> | ||||
|  | @ -73,37 +80,74 @@ | |||
| 				</div> | ||||
| 
 | ||||
| 				{#if showOpenAI} | ||||
| 					<div> | ||||
| 						<div class=" mb-2.5 text-sm font-medium">{$i18n.t('API Key')}</div> | ||||
| 						<div class="flex w-full"> | ||||
| 							<div class="flex-1"> | ||||
| 								<input | ||||
| 									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" | ||||
| 									placeholder={$i18n.t('Enter OpenAI API Key')} | ||||
| 									bind:value={OPENAI_API_KEY} | ||||
| 									autocomplete="off" | ||||
| 								/> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 					<div class="flex flex-col gap-1"> | ||||
| 						{#each OPENAI_API_BASE_URLS as url, idx} | ||||
| 							<div class="flex w-full gap-2"> | ||||
| 								<div class="flex-1"> | ||||
| 									<input | ||||
| 										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" | ||||
| 										placeholder={$i18n.t('API Base URL')} | ||||
| 										bind:value={url} | ||||
| 										autocomplete="off" | ||||
| 									/> | ||||
| 								</div> | ||||
| 
 | ||||
| 					<div> | ||||
| 						<div class=" mb-2.5 text-sm font-medium">{$i18n.t('API Base URL')}</div> | ||||
| 						<div class="flex w-full"> | ||||
| 							<div class="flex-1"> | ||||
| 								<input | ||||
| 									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" | ||||
| 									placeholder="Enter OpenAI API Base URL" | ||||
| 									bind:value={OPENAI_API_BASE_URL} | ||||
| 									autocomplete="off" | ||||
| 								/> | ||||
| 								<div class="flex-1"> | ||||
| 									<input | ||||
| 										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" | ||||
| 										placeholder={$i18n.t('API Key')} | ||||
| 										bind:value={OPENAI_API_KEYS[idx]} | ||||
| 										autocomplete="off" | ||||
| 									/> | ||||
| 								</div> | ||||
| 								<div class="self-center flex items-center"> | ||||
| 									{#if idx === 0} | ||||
| 										<button | ||||
| 											class="px-1" | ||||
| 											on:click={() => { | ||||
| 												OPENAI_API_BASE_URLS = [...OPENAI_API_BASE_URLS, '']; | ||||
| 												OPENAI_API_KEYS = [...OPENAI_API_KEYS, '']; | ||||
| 											}} | ||||
| 											type="button" | ||||
| 										> | ||||
| 											<svg | ||||
| 												xmlns="http://www.w3.org/2000/svg" | ||||
| 												viewBox="0 0 16 16" | ||||
| 												fill="currentColor" | ||||
| 												class="w-4 h-4" | ||||
| 											> | ||||
| 												<path | ||||
| 													d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z" | ||||
| 												/> | ||||
| 											</svg> | ||||
| 										</button> | ||||
| 									{:else} | ||||
| 										<button | ||||
| 											class="px-1" | ||||
| 											on:click={() => { | ||||
| 												OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.filter( | ||||
| 													(url, urlIdx) => idx !== urlIdx | ||||
| 												); | ||||
| 												OPENAI_API_KEYS = OPENAI_API_KEYS.filter((key, keyIdx) => idx !== keyIdx); | ||||
| 											}} | ||||
| 											type="button" | ||||
| 										> | ||||
| 											<svg | ||||
| 												xmlns="http://www.w3.org/2000/svg" | ||||
| 												viewBox="0 0 16 16" | ||||
| 												fill="currentColor" | ||||
| 												class="w-4 h-4" | ||||
| 											> | ||||
| 												<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" /> | ||||
| 											</svg> | ||||
| 										</button> | ||||
| 									{/if} | ||||
| 								</div> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 						<div class="mt-2 text-xs text-gray-400 dark:text-gray-500"> | ||||
| 							WebUI will make requests to <span class=" text-gray-200" | ||||
| 								>'{OPENAI_API_BASE_URL}/chat'</span | ||||
| 							> | ||||
| 						</div> | ||||
| 							<div class=" mb-1 text-xs text-gray-400 dark:text-gray-500"> | ||||
| 								WebUI will make requests to <span class=" text-gray-200">'{url}/models'</span> | ||||
| 							</div> | ||||
| 						{/each} | ||||
| 					</div> | ||||
| 				{/if} | ||||
| 			</div> | ||||
|  |  | |||
|  | @ -56,7 +56,7 @@ | |||
| 	let modelUploadMode = 'file'; | ||||
| 	let modelInputFile = ''; | ||||
| 	let modelFileUrl = ''; | ||||
| 	let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSSISTANT:"`; | ||||
| 	let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`; | ||||
| 	let modelFileDigest = ''; | ||||
| 	let uploadProgress = null; | ||||
| 
 | ||||
|  | @ -517,7 +517,7 @@ | |||
| 									{#if !deleteModelTag} | ||||
| 										<option value="" disabled selected>Select a model</option> | ||||
| 									{/if} | ||||
| 									{#each $models.filter((m) => m.size != null) as model} | ||||
| 									{#each $models.filter((m) => m.size != null && (selectedOllamaUrlIdx === null ? true : (m?.urls ?? []).includes(selectedOllamaUrlIdx))) as model} | ||||
| 										<option value={model.name} class="bg-gray-100 dark:bg-gray-700" | ||||
| 											>{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option | ||||
| 										> | ||||
|  | @ -599,7 +599,7 @@ | |||
| 												on:change={() => { | ||||
| 													console.log(modelInputFile); | ||||
| 												}} | ||||
| 												accept=".gguf" | ||||
| 												accept=".gguf,.safetensors" | ||||
| 												required | ||||
| 												hidden | ||||
| 											/> | ||||
|  |  | |||
|  | @ -140,7 +140,9 @@ | |||
| 						<button | ||||
| 							class="w-full text-sm font-medium py-3 bg-gray-850 hover:bg-gray-800 text-center rounded-xl" | ||||
| 							type="button" | ||||
| 							on:click={uploadDocInputElement.click} | ||||
| 							on:click={() => { | ||||
| 								uploadDocInputElement.click(); | ||||
| 							}} | ||||
| 						> | ||||
| 							{#if inputFiles} | ||||
| 								{inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected. | ||||
|  |  | |||
|  | @ -90,8 +90,3 @@ export const SUPPORTED_FILE_EXTENSIONS = [ | |||
| // This feature, akin to $env/static/private, exclusively incorporates environment variables
 | ||||
| // that are prefixed with config.kit.env.publicPrefix (usually set to PUBLIC_).
 | ||||
| // Consequently, these variables can be securely exposed to client-side code.
 | ||||
| 
 | ||||
| // Example of the .env configuration:
 | ||||
| // OLLAMA_API_BASE_URL="http://localhost:11434/api"
 | ||||
| // # Public
 | ||||
| // PUBLIC_API_BASE_URL=$OLLAMA_API_BASE_URL
 | ||||
|  |  | |||
|  | @ -99,14 +99,11 @@ | |||
| 					if (localDBChats.length === 0) { | ||||
| 						await deleteDB('Chats'); | ||||
| 					} | ||||
| 
 | ||||
| 					console.log('localdb', localDBChats); | ||||
| 				} | ||||
| 
 | ||||
| 				console.log(DB); | ||||
| 			} catch (error) { | ||||
| 				// IndexedDB Not Found | ||||
| 				console.log('IDB Not Found'); | ||||
| 			} | ||||
| 
 | ||||
| 			console.log(); | ||||
|  |  | |||
|  | @ -344,7 +344,7 @@ | |||
| 						content: $settings.system | ||||
| 				  } | ||||
| 				: undefined, | ||||
| 			...messages.filter((message) => !message.deleted) | ||||
| 			...messages | ||||
| 		] | ||||
| 			.filter((message) => message) | ||||
| 			.map((message, idx, arr) => ({ | ||||
|  | @ -558,7 +558,7 @@ | |||
| 								content: $settings.system | ||||
| 						  } | ||||
| 						: undefined, | ||||
| 					...messages.filter((message) => !message.deleted) | ||||
| 					...messages | ||||
| 				] | ||||
| 					.filter((message) => message) | ||||
| 					.map((message, idx, arr) => ({ | ||||
|  |  | |||
|  | @ -354,7 +354,7 @@ | |||
| 						content: $settings.system | ||||
| 				  } | ||||
| 				: undefined, | ||||
| 			...messages.filter((message) => !message.deleted) | ||||
| 			...messages | ||||
| 		] | ||||
| 			.filter((message) => message) | ||||
| 			.map((message, idx, arr) => ({ | ||||
|  | @ -568,7 +568,7 @@ | |||
| 								content: $settings.system | ||||
| 						  } | ||||
| 						: undefined, | ||||
| 					...messages.filter((message) => !message.deleted) | ||||
| 					...messages | ||||
| 				] | ||||
| 					.filter((message) => message) | ||||
| 					.map((message, idx, arr) => ({ | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Ased Mammad
						Ased Mammad