mirror of
				https://github.com/open-webui/open-webui
				synced 2025-06-26 18:26:48 +00:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/dev' into docling_context_extraction_engine
merge upstream dev
This commit is contained in:
		
						commit
						de0f158b04
					
				
							
								
								
									
										8
									
								
								.github/ISSUE_TEMPLATE/bug_report.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/ISSUE_TEMPLATE/bug_report.yaml
									
									
									
									
										vendored
									
									
								
							| @ -27,6 +27,8 @@ body: | ||||
|       options: | ||||
|         - label: I have searched the existing issues and discussions. | ||||
|           required: true | ||||
|         - label: I am using the latest version of Open WebUI. | ||||
|           required: true | ||||
| 
 | ||||
|   - type: dropdown | ||||
|     id: installation-method | ||||
| @ -83,9 +85,9 @@ body: | ||||
|           required: true | ||||
|         - label: I am using the latest version of **both** Open WebUI and Ollama. | ||||
|           required: true | ||||
|         - label: I have checked the browser console logs. | ||||
|         - label: I have included the browser console logs. | ||||
|           required: true | ||||
|         - label: I have checked the Docker container logs. | ||||
|         - label: I have included the Docker container logs. | ||||
|           required: true | ||||
|         - label: I have listed steps to reproduce the bug in detail. | ||||
|           required: true | ||||
| @ -110,7 +112,7 @@ body: | ||||
|     id: reproduction-steps | ||||
|     attributes: | ||||
|       label: Steps to Reproduce | ||||
|       description: Provide step-by-step instructions to reproduce the issue. | ||||
|       description: Providing clear, step-by-step instructions helps us reproduce and fix the issue faster. If we can't reproduce it, we can't fix it. | ||||
|       placeholder: | | ||||
|         1. Go to '...' | ||||
|         2. Click on '...' | ||||
|  | ||||
							
								
								
									
										1
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| blank_issues_enabled: false | ||||
| @ -5,6 +5,7 @@ | ||||
| 	import Tooltip from '$lib/components/common/Tooltip.svelte'; | ||||
| 	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte'; | ||||
| 	import AddConnectionModal from '$lib/components/AddConnectionModal.svelte'; | ||||
| 	import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; | ||||
| 
 | ||||
| 	import Cog6 from '$lib/components/icons/Cog6.svelte'; | ||||
| 	import Wrench from '$lib/components/icons/Wrench.svelte'; | ||||
| @ -20,6 +21,7 @@ | ||||
| 
 | ||||
| 	let showManageModal = false; | ||||
| 	let showConfigModal = false; | ||||
| 	let showDeleteConfirmDialog = false; | ||||
| </script> | ||||
| 
 | ||||
| <AddConnectionModal | ||||
| @ -31,7 +33,9 @@ | ||||
| 		key: config?.key ?? '', | ||||
| 		config: config | ||||
| 	}} | ||||
| 	{onDelete} | ||||
| 	onDelete={() => { | ||||
| 		showDeleteConfirmDialog = true; | ||||
| 	}} | ||||
| 	onSubmit={(connection) => { | ||||
| 		url = connection.url; | ||||
| 		config = { ...connection.config, key: connection.key }; | ||||
| @ -39,6 +43,14 @@ | ||||
| 	}} | ||||
| /> | ||||
| 
 | ||||
| <ConfirmDialog | ||||
| 	bind:show={showDeleteConfirmDialog} | ||||
| 	on:confirm={() => { | ||||
| 		onDelete(); | ||||
| 		showConfigModal = false; | ||||
| 	}} | ||||
| /> | ||||
| 
 | ||||
| <ManageOllamaModal bind:show={showManageModal} urlIdx={idx} /> | ||||
| 
 | ||||
| <div class="flex gap-1.5"> | ||||
|  | ||||
| @ -6,6 +6,7 @@ | ||||
| 	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte'; | ||||
| 	import Cog6 from '$lib/components/icons/Cog6.svelte'; | ||||
| 	import AddConnectionModal from '$lib/components/AddConnectionModal.svelte'; | ||||
| 	import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; | ||||
| 
 | ||||
| 	import { connect } from 'socket.io-client'; | ||||
| 
 | ||||
| @ -19,8 +20,16 @@ | ||||
| 	export let config = {}; | ||||
| 
 | ||||
| 	let showConfigModal = false; | ||||
| 	let showDeleteConfirmDialog = false; | ||||
| </script> | ||||
| 
 | ||||
| <ConfirmDialog | ||||
| 	bind:show={showDeleteConfirmDialog} | ||||
| 	on:confirm={() => { | ||||
| 		onDelete(); | ||||
| 	}} | ||||
| /> | ||||
| 
 | ||||
| <AddConnectionModal | ||||
| 	edit | ||||
| 	bind:show={showConfigModal} | ||||
| @ -29,7 +38,9 @@ | ||||
| 		key, | ||||
| 		config | ||||
| 	}} | ||||
| 	{onDelete} | ||||
| 	onDelete={() => { | ||||
| 		showDeleteConfirmDialog = true; | ||||
| 	}} | ||||
| 	onSubmit={(connection) => { | ||||
| 		url = connection.url; | ||||
| 		key = connection.key; | ||||
|  | ||||
| @ -10,6 +10,7 @@ | ||||
| 	import PencilSolid from '$lib/components/icons/PencilSolid.svelte'; | ||||
| 	import { toast } from 'svelte-sonner'; | ||||
| 	import AccessControl from '$lib/components/workspace/common/AccessControl.svelte'; | ||||
| 	import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; | ||||
| 
 | ||||
| 	export let show = false; | ||||
| 	export let edit = false; | ||||
| @ -44,6 +45,7 @@ | ||||
| 
 | ||||
| 	let imageInputElement; | ||||
| 	let loading = false; | ||||
| 	let showDeleteConfirmDialog = false; | ||||
| 
 | ||||
| 	const addModelHandler = () => { | ||||
| 		if (selectedModelId) { | ||||
| @ -115,6 +117,14 @@ | ||||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| <ConfirmDialog | ||||
| 	bind:show={showDeleteConfirmDialog} | ||||
| 	on:confirm={() => { | ||||
| 		dispatch('delete', model); | ||||
| 		show = false; | ||||
| 	}} | ||||
| /> | ||||
| 
 | ||||
| <Modal size="sm" bind:show> | ||||
| 	<div> | ||||
| 		<div class=" flex justify-between dark:text-gray-100 px-5 pt-4 pb-2"> | ||||
| @ -378,8 +388,7 @@ | ||||
| 								class="px-3.5 py-1.5 text-sm font-medium dark:bg-black dark:hover:bg-gray-950 dark:text-white bg-white text-black hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center" | ||||
| 								type="button" | ||||
| 								on:click={() => { | ||||
| 									dispatch('delete', model); | ||||
| 									show = false; | ||||
| 									showDeleteConfirmDialog = true; | ||||
| 								}} | ||||
| 							> | ||||
| 								{$i18n.t('Delete')} | ||||
|  | ||||
| @ -284,6 +284,8 @@ | ||||
| 							> | ||||
| 								{$i18n.t('Delete')} | ||||
| 							</button> | ||||
| 						{:else} | ||||
| 							<div></div> | ||||
| 						{/if} | ||||
| 
 | ||||
| 						<button | ||||
|  | ||||
| @ -12,6 +12,7 @@ | ||||
| 	import Modal from '$lib/components/common/Modal.svelte'; | ||||
| 	import Tooltip from '$lib/components/common/Tooltip.svelte'; | ||||
| 	import Spinner from '$lib/components/common/Spinner.svelte'; | ||||
| 	import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| @ -19,6 +20,8 @@ | ||||
| 	export let user; | ||||
| 
 | ||||
| 	let chats = null; | ||||
| 	let showDeleteConfirmDialog = false; | ||||
| 	let chatToDelete = null; | ||||
| 
 | ||||
| 	const deleteChatHandler = async (chatId) => { | ||||
| 		const res = await deleteChatById(localStorage.token, chatId).catch((error) => { | ||||
| @ -50,6 +53,16 @@ | ||||
| 	} | ||||
| </script> | ||||
| 
 | ||||
| <ConfirmDialog | ||||
| 	bind:show={showDeleteConfirmDialog} | ||||
| 	on:confirm={() => { | ||||
| 		if (chatToDelete) { | ||||
| 			deleteChatHandler(chatToDelete); | ||||
| 			chatToDelete = null; | ||||
| 		} | ||||
| 	}} | ||||
| /> | ||||
| 
 | ||||
| <Modal size="lg" bind:show> | ||||
| 	<div class=" flex justify-between dark:text-gray-300 px-5 pt-4"> | ||||
| 		<div class=" text-lg font-medium self-center capitalize"> | ||||
| @ -142,7 +155,8 @@ | ||||
| 														<button | ||||
| 															class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl" | ||||
| 															on:click={async () => { | ||||
| 																deleteChatHandler(chat.id); | ||||
| 																chatToDelete = chat.id; | ||||
| 																showDeleteConfirmDialog = true; | ||||
| 															}} | ||||
| 														> | ||||
| 															<svg | ||||
|  | ||||
| @ -30,45 +30,45 @@ | ||||
| 		</button> | ||||
| 	</div> | ||||
| 
 | ||||
| 	{#if $user.role === 'admin' || $user?.permissions.chat?.controls} | ||||
| 		<div class=" dark:text-gray-200 text-sm font-primary py-0.5 px-0.5"> | ||||
| 			{#if chatFiles.length > 0} | ||||
| 				<Collapsible title={$i18n.t('Files')} open={true} buttonClassName="w-full"> | ||||
| 					<div class="flex flex-col gap-1 mt-1.5" slot="content"> | ||||
| 						{#each chatFiles as file, fileIdx} | ||||
| 							<FileItem | ||||
| 								className="w-full" | ||||
| 								item={file} | ||||
| 								edit={true} | ||||
| 								url={file?.url ? file.url : null} | ||||
| 								name={file.name} | ||||
| 								type={file.type} | ||||
| 								size={file?.size} | ||||
| 								dismissible={true} | ||||
| 								on:dismiss={() => { | ||||
| 									// Remove the file from the chatFiles array | ||||
| 	<div class=" dark:text-gray-200 text-sm font-primary py-0.5 px-0.5"> | ||||
| 		{#if chatFiles.length > 0} | ||||
| 			<Collapsible title={$i18n.t('Files')} open={true} buttonClassName="w-full"> | ||||
| 				<div class="flex flex-col gap-1 mt-1.5" slot="content"> | ||||
| 					{#each chatFiles as file, fileIdx} | ||||
| 						<FileItem | ||||
| 							className="w-full" | ||||
| 							item={file} | ||||
| 							edit={true} | ||||
| 							url={file?.url ? file.url : null} | ||||
| 							name={file.name} | ||||
| 							type={file.type} | ||||
| 							size={file?.size} | ||||
| 							dismissible={true} | ||||
| 							on:dismiss={() => { | ||||
| 								// Remove the file from the chatFiles array | ||||
| 
 | ||||
| 									chatFiles.splice(fileIdx, 1); | ||||
| 									chatFiles = chatFiles; | ||||
| 								}} | ||||
| 								on:click={() => { | ||||
| 									console.log(file); | ||||
| 								}} | ||||
| 							/> | ||||
| 						{/each} | ||||
| 					</div> | ||||
| 				</Collapsible> | ||||
| 
 | ||||
| 				<hr class="my-2 border-gray-50 dark:border-gray-700/10" /> | ||||
| 			{/if} | ||||
| 
 | ||||
| 			<Collapsible bind:open={showValves} title={$i18n.t('Valves')} buttonClassName="w-full"> | ||||
| 				<div class="text-sm" slot="content"> | ||||
| 					<Valves show={showValves} /> | ||||
| 								chatFiles.splice(fileIdx, 1); | ||||
| 								chatFiles = chatFiles; | ||||
| 							}} | ||||
| 							on:click={() => { | ||||
| 								console.log(file); | ||||
| 							}} | ||||
| 						/> | ||||
| 					{/each} | ||||
| 				</div> | ||||
| 			</Collapsible> | ||||
| 
 | ||||
| 			<hr class="my-2 border-gray-50 dark:border-gray-700/10" /> | ||||
| 		{/if} | ||||
| 
 | ||||
| 		<Collapsible bind:open={showValves} title={$i18n.t('Valves')} buttonClassName="w-full"> | ||||
| 			<div class="text-sm" slot="content"> | ||||
| 				<Valves show={showValves} /> | ||||
| 			</div> | ||||
| 		</Collapsible> | ||||
| 
 | ||||
| 		{#if $user.role === 'admin' || $user?.permissions.chat?.controls} | ||||
| 			<hr class="my-2 border-gray-50 dark:border-gray-700/10" /> | ||||
| 
 | ||||
| 			<Collapsible title={$i18n.t('System Prompt')} open={true} buttonClassName="w-full"> | ||||
| 				<div class="" slot="content"> | ||||
| @ -90,10 +90,6 @@ | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</Collapsible> | ||||
| 		</div> | ||||
| 	{:else} | ||||
| 		<div class="text-sm dark:text-gray-300 text-center py-2 px-10"> | ||||
| 			{$i18n.t('You do not have permission to access this feature.')} | ||||
| 		</div> | ||||
| 	{/if} | ||||
| 		{/if} | ||||
| 	</div> | ||||
| </div> | ||||
|  | ||||
| @ -61,7 +61,9 @@ | ||||
| 	$: selectedModel = items.find((item) => item.value === value) ?? ''; | ||||
| 
 | ||||
| 	let searchValue = ''; | ||||
| 
 | ||||
| 	let selectedTag = ''; | ||||
| 	let selectedConnectionType = ''; | ||||
| 
 | ||||
| 	let ollamaVersion = null; | ||||
| 
 | ||||
| @ -95,12 +97,35 @@ | ||||
| 					} | ||||
| 					return item.model?.info?.meta?.tags?.map((tag) => tag.name).includes(selectedTag); | ||||
| 				}) | ||||
| 		: items.filter((item) => { | ||||
| 				if (selectedTag === '') { | ||||
| 					return true; | ||||
| 				} | ||||
| 				return item.model?.info?.meta?.tags?.map((tag) => tag.name).includes(selectedTag); | ||||
| 			}); | ||||
| 				.filter((item) => { | ||||
| 					if (selectedConnectionType === '') { | ||||
| 						return true; | ||||
| 					} else if (selectedConnectionType === 'ollama') { | ||||
| 						return item.model?.owned_by === 'ollama'; | ||||
| 					} else if (selectedConnectionType === 'openai') { | ||||
| 						return item.model?.owned_by === 'openai'; | ||||
| 					} else if (selectedConnectionType === 'direct') { | ||||
| 						return item.model?.direct; | ||||
| 					} | ||||
| 				}) | ||||
| 		: items | ||||
| 				.filter((item) => { | ||||
| 					if (selectedTag === '') { | ||||
| 						return true; | ||||
| 					} | ||||
| 					return item.model?.info?.meta?.tags?.map((tag) => tag.name).includes(selectedTag); | ||||
| 				}) | ||||
| 				.filter((item) => { | ||||
| 					if (selectedConnectionType === '') { | ||||
| 						return true; | ||||
| 					} else if (selectedConnectionType === 'ollama') { | ||||
| 						return item.model?.owned_by === 'ollama'; | ||||
| 					} else if (selectedConnectionType === 'openai') { | ||||
| 						return item.model?.owned_by === 'openai'; | ||||
| 					} else if (selectedConnectionType === 'direct') { | ||||
| 						return item.model?.direct; | ||||
| 					} | ||||
| 				}); | ||||
| 
 | ||||
| 	const pullModelHandler = async () => { | ||||
| 		const sanitizedModelTag = searchValue.trim().replace(/^ollama\s+(run|pull)\s+/, ''); | ||||
| @ -326,43 +351,62 @@ | ||||
| 
 | ||||
| 			<div class="px-3 mb-2 max-h-64 overflow-y-auto scrollbar-hidden group relative"> | ||||
| 				{#if tags} | ||||
| 					<div class=" flex w-full sticky"> | ||||
| 					<div class=" flex w-full sticky top-0 z-10 bg-white dark:bg-gray-850"> | ||||
| 						<div | ||||
| 							class="flex gap-1 scrollbar-none overflow-x-auto w-fit text-center text-sm font-medium rounded-full bg-transparent px-1.5 pb-0.5" | ||||
| 							bind:this={tagsContainerElement} | ||||
| 						> | ||||
| 							<button | ||||
| 								class="min-w-fit outline-none p-1.5 {selectedTag === '' | ||||
| 								class="min-w-fit outline-none p-1.5 {selectedTag === '' && | ||||
| 								selectedConnectionType === '' | ||||
| 									? '' | ||||
| 									: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize" | ||||
| 								on:click={() => { | ||||
| 									selectedConnectionType = ''; | ||||
| 									selectedTag = ''; | ||||
| 								}} | ||||
| 							> | ||||
| 								{$i18n.t('All')} | ||||
| 							</button> | ||||
| 
 | ||||
| 							<button | ||||
| 								class="min-w-fit outline-none p-1.5 {selectedTag === '' | ||||
| 									? '' | ||||
| 									: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize" | ||||
| 								on:click={() => { | ||||
| 									selectedTag = ''; | ||||
| 								}} | ||||
| 							> | ||||
| 								{$i18n.t('Ollama')} | ||||
| 							</button> | ||||
| 							{#if items.find((item) => item.model?.owned_by === 'ollama') && items.find((item) => item.model?.owned_by === 'openai')} | ||||
| 								<button | ||||
| 									class="min-w-fit outline-none p-1.5 {selectedConnectionType === 'ollama' | ||||
| 										? '' | ||||
| 										: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize" | ||||
| 									on:click={() => { | ||||
| 										selectedTag = ''; | ||||
| 										selectedConnectionType = 'ollama'; | ||||
| 									}} | ||||
| 								> | ||||
| 									{$i18n.t('Local')} | ||||
| 								</button> | ||||
| 								<button | ||||
| 									class="min-w-fit outline-none p-1.5 {selectedConnectionType === 'openai' | ||||
| 										? '' | ||||
| 										: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize" | ||||
| 									on:click={() => { | ||||
| 										selectedTag = ''; | ||||
| 										selectedConnectionType = 'openai'; | ||||
| 									}} | ||||
| 								> | ||||
| 									{$i18n.t('External')} | ||||
| 								</button> | ||||
| 							{/if} | ||||
| 
 | ||||
| 							<button | ||||
| 								class="min-w-fit outline-none p-1.5 {selectedTag === '' | ||||
| 									? '' | ||||
| 									: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize" | ||||
| 								on:click={() => { | ||||
| 									selectedTag = ''; | ||||
| 								}} | ||||
| 							> | ||||
| 								{$i18n.t('OpenAI')} | ||||
| 							</button> | ||||
| 							{#if items.find((item) => item.model?.direct)} | ||||
| 								<button | ||||
| 									class="min-w-fit outline-none p-1.5 {selectedConnectionType === 'direct' | ||||
| 										? '' | ||||
| 										: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize" | ||||
| 									on:click={() => { | ||||
| 										selectedTag = ''; | ||||
| 										selectedConnectionType = 'direct'; | ||||
| 									}} | ||||
| 								> | ||||
| 									{$i18n.t('Direct')} | ||||
| 								</button> | ||||
| 							{/if} | ||||
| 
 | ||||
| 							{#each tags as tag} | ||||
| 								<button | ||||
| @ -370,6 +414,7 @@ | ||||
| 										? '' | ||||
| 										: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize" | ||||
| 									on:click={() => { | ||||
| 										selectedConnectionType = ''; | ||||
| 										selectedTag = tag; | ||||
| 									}} | ||||
| 								> | ||||
|  | ||||
| @ -114,37 +114,21 @@ | ||||
| 							</div> | ||||
| 						</button> | ||||
| 					</Menu> | ||||
| 				{:else if $mobile && ($user.role === 'admin' || $user?.permissions?.chat?.controls)} | ||||
| 					<Tooltip content={$i18n.t('Controls')}> | ||||
| 						<button | ||||
| 							class=" flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition" | ||||
| 							on:click={async () => { | ||||
| 								await showControls.set(!$showControls); | ||||
| 							}} | ||||
| 							aria-label="Controls" | ||||
| 						> | ||||
| 							<div class=" m-auto self-center"> | ||||
| 								<AdjustmentsHorizontal className=" size-5" strokeWidth="0.5" /> | ||||
| 							</div> | ||||
| 						</button> | ||||
| 					</Tooltip> | ||||
| 				{/if} | ||||
| 
 | ||||
| 				{#if !$mobile && ($user.role === 'admin' || $user?.permissions?.chat?.controls)} | ||||
| 					<Tooltip content={$i18n.t('Controls')}> | ||||
| 						<button | ||||
| 							class=" flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition" | ||||
| 							on:click={async () => { | ||||
| 								await showControls.set(!$showControls); | ||||
| 							}} | ||||
| 							aria-label="Controls" | ||||
| 						> | ||||
| 							<div class=" m-auto self-center"> | ||||
| 								<AdjustmentsHorizontal className=" size-5" strokeWidth="0.5" /> | ||||
| 							</div> | ||||
| 						</button> | ||||
| 					</Tooltip> | ||||
| 				{/if} | ||||
| 				<Tooltip content={$i18n.t('Controls')}> | ||||
| 					<button | ||||
| 						class=" flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition" | ||||
| 						on:click={async () => { | ||||
| 							await showControls.set(!$showControls); | ||||
| 						}} | ||||
| 						aria-label="Controls" | ||||
| 					> | ||||
| 						<div class=" m-auto self-center"> | ||||
| 							<AdjustmentsHorizontal className=" size-5" strokeWidth="0.5" /> | ||||
| 						</div> | ||||
| 					</button> | ||||
| 				</Tooltip> | ||||
| 
 | ||||
| 				<Tooltip content={$i18n.t('New Chat')}> | ||||
| 					<button | ||||
|  | ||||
| @ -6,6 +6,7 @@ | ||||
| 	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte'; | ||||
| 	import Cog6 from '$lib/components/icons/Cog6.svelte'; | ||||
| 	import AddConnectionModal from '$lib/components/AddConnectionModal.svelte'; | ||||
| 	import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; | ||||
| 
 | ||||
| 	export let onDelete = () => {}; | ||||
| 	export let onSubmit = () => {}; | ||||
| @ -17,6 +18,7 @@ | ||||
| 	export let config = {}; | ||||
| 
 | ||||
| 	let showConfigModal = false; | ||||
| 	let showDeleteConfirmDialog = false; | ||||
| </script> | ||||
| 
 | ||||
| <AddConnectionModal | ||||
| @ -28,7 +30,9 @@ | ||||
| 		key, | ||||
| 		config | ||||
| 	}} | ||||
| 	{onDelete} | ||||
| 	onDelete={() => { | ||||
| 		showDeleteConfirmDialog = true; | ||||
| 	}} | ||||
| 	onSubmit={(connection) => { | ||||
| 		url = connection.url; | ||||
| 		key = connection.key; | ||||
| @ -37,6 +41,14 @@ | ||||
| 	}} | ||||
| /> | ||||
| 
 | ||||
| <ConfirmDialog | ||||
| 	bind:show={showDeleteConfirmDialog} | ||||
| 	on:confirm={() => { | ||||
| 		onDelete(); | ||||
| 		showConfigModal = false; | ||||
| 	}} | ||||
| /> | ||||
| 
 | ||||
| <div class="flex w-full gap-2 items-center"> | ||||
| 	<Tooltip | ||||
| 		className="w-full relative" | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| <script lang="ts"> | ||||
| 	import { onDestroy, onMount } from 'svelte'; | ||||
| 	import panzoom, { type PanZoom } from 'panzoom'; | ||||
| 
 | ||||
| 	export let show = false; | ||||
| 	export let src = ''; | ||||
| @ -9,6 +10,25 @@ | ||||
| 
 | ||||
| 	let previewElement = null; | ||||
| 
 | ||||
| 	let instance: PanZoom; | ||||
| 
 | ||||
| 	let sceneParentElement: HTMLElement; | ||||
| 	let sceneElement: HTMLElement; | ||||
| 
 | ||||
| 	$: if (sceneElement) { | ||||
| 		instance = panzoom(sceneElement, { | ||||
| 			bounds: true, | ||||
| 			boundsPadding: 0.1, | ||||
| 
 | ||||
| 			zoomSpeed: 0.065 | ||||
| 		}); | ||||
| 	} | ||||
| 	const resetPanZoomViewport = () => { | ||||
| 		instance.moveTo(0, 0); | ||||
| 		instance.zoomAbs(0, 0, 1); | ||||
| 		console.log(instance.getTransform()); | ||||
| 	}; | ||||
| 
 | ||||
| 	const downloadImage = (url, filename, prefixName = '') => { | ||||
| 		fetch(url) | ||||
| 			.then((response) => response.blob()) | ||||
| @ -106,6 +126,8 @@ | ||||
| 				</button> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<img {src} {alt} class=" mx-auto h-full object-scale-down select-none" draggable="false" /> | ||||
| 		<div bind:this={sceneElement} class="flex h-full max-h-full justify-center items-center"> | ||||
| 			<img {src} {alt} class=" mx-auto h-full object-scale-down select-none" draggable="false" /> | ||||
| 		</div> | ||||
| 	</div> | ||||
| {/if} | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user