forked from open-webui/open-webui
		
	Merge remote-tracking branch 'upstream/dev' into feat/add-i18n
This commit is contained in:
		
						commit
						25e0f0de42
					
				
					 26 changed files with 1445 additions and 668 deletions
				
			
		|  | @ -34,12 +34,13 @@ | |||
| 	import Sidebar from '$lib/components/layout/Sidebar.svelte'; | ||||
| 	import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte'; | ||||
| 	import ChangelogModal from '$lib/components/ChangelogModal.svelte'; | ||||
| 	import Tooltip from '$lib/components/common/Tooltip.svelte'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| 	let ollamaVersion = ''; | ||||
| 	let loaded = false; | ||||
| 
 | ||||
| 	let showShortcutsButtonElement: HTMLButtonElement; | ||||
| 	let DB = null; | ||||
| 	let localDBChats = []; | ||||
| 
 | ||||
|  | @ -186,7 +187,7 @@ | |||
| 				if (isCtrlPressed && event.key === '/') { | ||||
| 					event.preventDefault(); | ||||
| 					console.log('showShortcuts'); | ||||
| 					document.getElementById('show-shortcuts-button')?.click(); | ||||
| 					showShortcutsButtonElement.click(); | ||||
| 				} | ||||
| 			}); | ||||
| 
 | ||||
|  | @ -203,15 +204,18 @@ | |||
| 
 | ||||
| {#if loaded} | ||||
| 	<div class=" hidden lg:flex fixed bottom-0 right-0 px-3 py-3 z-10"> | ||||
| 		<button | ||||
| 			id="show-shortcuts-button" | ||||
| 			class="text-gray-600 dark:text-gray-300 bg-gray-300/20 w-6 h-6 flex items-center justify-center text-xs rounded-full" | ||||
| 			on:click={() => { | ||||
| 				showShortcuts = !showShortcuts; | ||||
| 			}} | ||||
| 		> | ||||
| 			? | ||||
| 		</button> | ||||
| 		<Tooltip content="help" placement="left"> | ||||
| 			<button | ||||
| 				id="show-shortcuts-button" | ||||
| 				bind:this={showShortcutsButtonElement} | ||||
| 				class="text-gray-600 dark:text-gray-300 bg-gray-300/20 w-6 h-6 flex items-center justify-center text-xs rounded-full" | ||||
| 				on:click={() => { | ||||
| 					showShortcuts = !showShortcuts; | ||||
| 				}} | ||||
| 			> | ||||
| 				? | ||||
| 			</button> | ||||
| 		</Tooltip> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<ShortcutsModal bind:show={showShortcuts} /> | ||||
|  |  | |||
|  | @ -45,7 +45,7 @@ | |||
| 	let stopResponseFlag = false; | ||||
| 	let autoScroll = true; | ||||
| 	let processing = ''; | ||||
| 
 | ||||
| 	let messagesContainerElement: HTMLDivElement; | ||||
| 	let currentRequestId = null; | ||||
| 
 | ||||
| 	let selectedModels = ['']; | ||||
|  | @ -143,8 +143,7 @@ | |||
| 	}; | ||||
| 
 | ||||
| 	const scrollToBottom = () => { | ||||
| 		const element = document.getElementById('messages-container'); | ||||
| 		element.scrollTop = element.scrollHeight; | ||||
| 		messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight; | ||||
| 	}; | ||||
| 
 | ||||
| 	////////////////////////// | ||||
|  | @ -837,8 +836,11 @@ | |||
| 		<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 = e.target.scrollHeight - e.target.scrollTop <= e.target.clientHeight + 50; | ||||
| 				autoScroll = | ||||
| 					messagesContainerElement.scrollHeight - messagesContainerElement.scrollTop <= | ||||
| 					messagesContainerElement.clientHeight + 50; | ||||
| 			}} | ||||
| 		> | ||||
| 			<div | ||||
|  | @ -846,10 +848,7 @@ | |||
| 					? 'max-w-full' | ||||
| 					: 'max-w-2xl md:px-0'} mx-auto w-full px-4" | ||||
| 			> | ||||
| 				<ModelSelector | ||||
| 					bind:selectedModels | ||||
| 					disabled={messages.length > 0 && !selectedModels.includes('')} | ||||
| 				/> | ||||
| 				<ModelSelector bind:selectedModels /> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div class=" h-full w-full flex flex-col py-8"> | ||||
|  |  | |||
|  | @ -47,7 +47,7 @@ | |||
| 	let stopResponseFlag = false; | ||||
| 	let autoScroll = true; | ||||
| 	let processing = ''; | ||||
| 
 | ||||
| 	let messagesContainerElement: HTMLDivElement; | ||||
| 	let currentRequestId = null; | ||||
| 
 | ||||
| 	// let chatId = $page.params.id; | ||||
|  | @ -162,8 +162,7 @@ | |||
| 	}; | ||||
| 
 | ||||
| 	const scrollToBottom = () => { | ||||
| 		const element = document.getElementById('messages-container'); | ||||
| 		element.scrollTop = element.scrollHeight; | ||||
| 		messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight; | ||||
| 	}; | ||||
| 
 | ||||
| 	////////////////////////// | ||||
|  | @ -865,8 +864,11 @@ | |||
| 			<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 = e.target.scrollHeight - e.target.scrollTop <= e.target.clientHeight + 50; | ||||
| 					autoScroll = | ||||
| 						messagesContainerElement.scrollHeight - messagesContainerElement.scrollTop <= | ||||
| 						messagesContainerElement.clientHeight + 50; | ||||
| 				}} | ||||
| 			> | ||||
| 				<div | ||||
|  | @ -874,10 +876,7 @@ | |||
| 						? 'max-w-full' | ||||
| 						: 'max-w-2xl md:px-0'} mx-auto w-full px-4" | ||||
| 				> | ||||
| 					<ModelSelector | ||||
| 						bind:selectedModels | ||||
| 						disabled={messages.length > 0 && !selectedModels.includes('')} | ||||
| 					/> | ||||
| 					<ModelSelector bind:selectedModels /> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div class=" h-full w-full flex flex-col py-8"> | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ | |||
| 
 | ||||
| 	let inputFiles = ''; | ||||
| 	let query = ''; | ||||
| 
 | ||||
| 	let documentsImportInputElement: HTMLInputElement; | ||||
| 	let tags = []; | ||||
| 
 | ||||
| 	let showSettingsModal = false; | ||||
|  | @ -527,6 +527,7 @@ | |||
| 				<div class="flex space-x-2"> | ||||
| 					<input | ||||
| 						id="documents-import-input" | ||||
| 						bind:this={documentsImportInputElement} | ||||
| 						bind:files={importFiles} | ||||
| 						type="file" | ||||
| 						accept=".json" | ||||
|  | @ -561,9 +562,7 @@ | |||
| 
 | ||||
| 					<button | ||||
| 						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition" | ||||
| 						on:click={async () => { | ||||
| 							document.getElementById('documents-import-input')?.click(); | ||||
| 						}} | ||||
| 						on:click={documentsImportInputElement.click} | ||||
| 					> | ||||
| 						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Documents Mapping')}</div> | ||||
| 
 | ||||
|  |  | |||
|  | @ -18,11 +18,14 @@ | |||
| 
 | ||||
| 	let localModelfiles = []; | ||||
| 	let importFiles; | ||||
| 
 | ||||
| 	let modelfilesImportInputElement: HTMLInputElement; | ||||
| 	const deleteModelHandler = async (tagName) => { | ||||
| 		let success = null; | ||||
| 
 | ||||
| 		success = await deleteModel(localStorage.token, tagName); | ||||
| 		success = await deleteModel(localStorage.token, tagName).catch((err) => { | ||||
| 			toast.error(err); | ||||
| 			return null; | ||||
| 		}); | ||||
| 
 | ||||
| 		if (success) { | ||||
| 			toast.success($i18n.t(`Deleted {tagName}`, { tagName })); | ||||
|  | @ -237,6 +240,7 @@ | |||
| 				<div class="flex space-x-1"> | ||||
| 					<input | ||||
| 						id="modelfiles-import-input" | ||||
| 						bind:this={modelfilesImportInputElement} | ||||
| 						bind:files={importFiles} | ||||
| 						type="file" | ||||
| 						accept=".json" | ||||
|  | @ -264,9 +268,7 @@ | |||
| 
 | ||||
| 					<button | ||||
| 						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition" | ||||
| 						on:click={async () => { | ||||
| 							document.getElementById('modelfiles-import-input')?.click(); | ||||
| 						}} | ||||
| 						on:click={modelfilesImportInputElement.click} | ||||
| 					> | ||||
| 						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Modelfiles')}</div> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| <script> | ||||
| <script lang="ts"> | ||||
| 	import { goto } from '$app/navigation'; | ||||
| 
 | ||||
| 	import { onMount, tick, getContext } from 'svelte'; | ||||
|  | @ -23,15 +23,17 @@ | |||
| 
 | ||||
| 	let mode = 'chat'; | ||||
| 	let loaded = false; | ||||
| 
 | ||||
| 	let text = ''; | ||||
| 
 | ||||
| 	let selectedModelId = ''; | ||||
| 
 | ||||
| 	let loading = false; | ||||
| 	let currentRequestId; | ||||
| 	let currentRequestId = null; | ||||
| 	let stopResponseFlag = false; | ||||
| 
 | ||||
| 	let messagesContainerElement: HTMLDivElement; | ||||
| 	let textCompletionAreaElement: HTMLTextAreaElement; | ||||
| 
 | ||||
| 	let system = ''; | ||||
| 	let messages = [ | ||||
| 		{ | ||||
|  | @ -41,13 +43,7 @@ | |||
| 	]; | ||||
| 
 | ||||
| 	const scrollToBottom = () => { | ||||
| 		let element; | ||||
| 
 | ||||
| 		if (mode === 'chat') { | ||||
| 			element = document.getElementById('messages-container'); | ||||
| 		} else { | ||||
| 			element = document.getElementById('text-completion-textarea'); | ||||
| 		} | ||||
| 		const element = mode === 'chat' ? messagesContainerElement : textCompletionAreaElement; | ||||
| 
 | ||||
| 		if (element) { | ||||
| 			element.scrollTop = element?.scrollHeight; | ||||
|  | @ -98,6 +94,10 @@ | |||
| 			while (true) { | ||||
| 				const { value, done } = await reader.read(); | ||||
| 				if (done || stopResponseFlag) { | ||||
| 					if (stopResponseFlag) { | ||||
| 						await cancelChatCompletion(localStorage.token, currentRequestId); | ||||
| 					} | ||||
| 
 | ||||
| 					currentRequestId = null; | ||||
| 					break; | ||||
| 				} | ||||
|  | @ -114,7 +114,11 @@ | |||
| 								let data = JSON.parse(line.replace(/^data: /, '')); | ||||
| 								console.log(data); | ||||
| 
 | ||||
| 								text += data.choices[0].delta.content ?? ''; | ||||
| 								if ('request_id' in data) { | ||||
| 									currentRequestId = data.request_id; | ||||
| 								} else { | ||||
| 									text += data.choices[0].delta.content ?? ''; | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
|  | @ -152,16 +156,6 @@ | |||
| 				: `${OLLAMA_API_BASE_URL}/v1` | ||||
| 		); | ||||
| 
 | ||||
| 		// const [res, controller] = await generateChatCompletion(localStorage.token, { | ||||
| 		// 	model: selectedModelId, | ||||
| 		// 	messages: [ | ||||
| 		// 		{ | ||||
| 		// 			role: 'assistant', | ||||
| 		// 			content: text | ||||
| 		// 		} | ||||
| 		// 	] | ||||
| 		// }); | ||||
| 
 | ||||
| 		let responseMessage; | ||||
| 		if (messages.at(-1)?.role === 'assistant') { | ||||
| 			responseMessage = messages.at(-1); | ||||
|  | @ -186,6 +180,11 @@ | |||
| 			while (true) { | ||||
| 				const { value, done } = await reader.read(); | ||||
| 				if (done || stopResponseFlag) { | ||||
| 					if (stopResponseFlag) { | ||||
| 						await cancelChatCompletion(localStorage.token, currentRequestId); | ||||
| 					} | ||||
| 
 | ||||
| 					currentRequestId = null; | ||||
| 					break; | ||||
| 				} | ||||
| 
 | ||||
|  | @ -202,17 +201,21 @@ | |||
| 								let data = JSON.parse(line.replace(/^data: /, '')); | ||||
| 								console.log(data); | ||||
| 
 | ||||
| 								if (responseMessage.content == '' && data.choices[0].delta.content == '\n') { | ||||
| 									continue; | ||||
| 								if ('request_id' in data) { | ||||
| 									currentRequestId = data.request_id; | ||||
| 								} else { | ||||
| 									textareaElement.style.height = textareaElement.scrollHeight + 'px'; | ||||
| 									if (responseMessage.content == '' && data.choices[0].delta.content == '\n') { | ||||
| 										continue; | ||||
| 									} else { | ||||
| 										textareaElement.style.height = textareaElement.scrollHeight + 'px'; | ||||
| 
 | ||||
| 									responseMessage.content += data.choices[0].delta.content ?? ''; | ||||
| 									messages = messages; | ||||
| 										responseMessage.content += data.choices[0].delta.content ?? ''; | ||||
| 										messages = messages; | ||||
| 
 | ||||
| 									textareaElement.style.height = textareaElement.scrollHeight + 'px'; | ||||
| 										textareaElement.style.height = textareaElement.scrollHeight + 'px'; | ||||
| 
 | ||||
| 									await tick(); | ||||
| 										await tick(); | ||||
| 									} | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
|  | @ -223,48 +226,6 @@ | |||
| 
 | ||||
| 				scrollToBottom(); | ||||
| 			} | ||||
| 
 | ||||
| 			// while (true) { | ||||
| 			// 	const { value, done } = await reader.read(); | ||||
| 			// 	if (done || stopResponseFlag) { | ||||
| 			// 		if (stopResponseFlag) { | ||||
| 			// 			await cancelChatCompletion(localStorage.token, currentRequestId); | ||||
| 			// 		} | ||||
| 
 | ||||
| 			// 		currentRequestId = null; | ||||
| 			// 		break; | ||||
| 			// 	} | ||||
| 
 | ||||
| 			// 	try { | ||||
| 			// 		let lines = value.split('\n'); | ||||
| 
 | ||||
| 			// 		for (const line of lines) { | ||||
| 			// 			if (line !== '') { | ||||
| 			// 				console.log(line); | ||||
| 			// 				let data = JSON.parse(line); | ||||
| 
 | ||||
| 			// 				if ('detail' in data) { | ||||
| 			// 					throw data; | ||||
| 			// 				} | ||||
| 
 | ||||
| 			// 				if ('id' in data) { | ||||
| 			// 					console.log(data); | ||||
| 			// 					currentRequestId = data.id; | ||||
| 			// 				} else { | ||||
| 			// 					if (data.done == false) { | ||||
| 			// 						text += data.message.content; | ||||
| 			// 					} else { | ||||
| 			// 						console.log('done'); | ||||
| 			// 					} | ||||
| 			// 				} | ||||
| 			// 			} | ||||
| 			// 		} | ||||
| 			// 	} catch (error) { | ||||
| 			// 		console.log(error); | ||||
| 			// 	} | ||||
| 
 | ||||
| 			// 	scrollToBottom(); | ||||
| 			// } | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
|  | @ -422,12 +383,14 @@ | |||
| 				<div | ||||
| 					class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0" | ||||
| 					id="messages-container" | ||||
| 					bind:this={messagesContainerElement} | ||||
| 				> | ||||
| 					<div class=" h-full w-full flex flex-col"> | ||||
| 						<div class="flex-1 p-1"> | ||||
| 							{#if mode === 'complete'} | ||||
| 								<textarea | ||||
| 									id="text-completion-textarea" | ||||
| 									bind:this={textCompletionAreaElement} | ||||
| 									class="w-full h-full p-3 bg-transparent outline outline-1 outline-gray-200 dark:outline-gray-800 resize-none rounded-lg text-sm" | ||||
| 									bind:value={text} | ||||
| 									placeholder={$i18n.t("You're a helpful assistant.")} | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
| 
 | ||||
| 	let importFiles = ''; | ||||
| 	let query = ''; | ||||
| 
 | ||||
| 	let promptsImportInputElement: HTMLInputElement; | ||||
| 	const sharePrompt = async (prompt) => { | ||||
| 		toast.success($i18n.t('Redirecting you to OpenWebUI Community')); | ||||
| 
 | ||||
|  | @ -210,6 +210,7 @@ | |||
| 				<div class="flex space-x-2"> | ||||
| 					<input | ||||
| 						id="prompts-import-input" | ||||
| 						bind:this={promptsImportInputElement} | ||||
| 						bind:files={importFiles} | ||||
| 						type="file" | ||||
| 						accept=".json" | ||||
|  | @ -243,9 +244,7 @@ | |||
| 
 | ||||
| 					<button | ||||
| 						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition" | ||||
| 						on:click={async () => { | ||||
| 							document.getElementById('prompts-import-input')?.click(); | ||||
| 						}} | ||||
| 						on:click={promptsImportInputElement.click} | ||||
| 					> | ||||
| 						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Prompts')}</div> | ||||
| 
 | ||||
|  | @ -268,7 +267,7 @@ | |||
| 					<button | ||||
| 						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition" | ||||
| 						on:click={async () => { | ||||
| 							// document.getElementById('modelfiles-import-input')?.click(); | ||||
| 							// promptsImportInputElement.click(); | ||||
| 							let blob = new Blob([JSON.stringify($prompts)], { | ||||
| 								type: 'application/json' | ||||
| 							}); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Ased Mammad
						Ased Mammad