forked from open-webui/open-webui
		
	Merge branch 'dev' into feat/customizable-title-prompt-length
This commit is contained in:
		
						commit
						a938ffb586
					
				
					 16 changed files with 187 additions and 43 deletions
				
			
		|  | @ -28,6 +28,7 @@ from config import ( | |||
|     UPLOAD_DIR, | ||||
|     WHISPER_MODEL, | ||||
|     WHISPER_MODEL_DIR, | ||||
|     WHISPER_MODEL_AUTO_UPDATE, | ||||
|     DEVICE_TYPE, | ||||
| ) | ||||
| 
 | ||||
|  | @ -69,12 +70,24 @@ def transcribe( | |||
|             f.write(contents) | ||||
|             f.close() | ||||
| 
 | ||||
|         model = WhisperModel( | ||||
|             WHISPER_MODEL, | ||||
|             device=whisper_device_type, | ||||
|             compute_type="int8", | ||||
|             download_root=WHISPER_MODEL_DIR, | ||||
|         whisper_kwargs = { | ||||
|             "model_size_or_path": WHISPER_MODEL, | ||||
|             "device": whisper_device_type, | ||||
|             "compute_type": "int8", | ||||
|             "download_root": WHISPER_MODEL_DIR, | ||||
|             "local_files_only": not WHISPER_MODEL_AUTO_UPDATE, | ||||
|         } | ||||
| 
 | ||||
|         log.debug(f"whisper_kwargs: {whisper_kwargs}") | ||||
| 
 | ||||
|         try: | ||||
|             model = WhisperModel(**whisper_kwargs) | ||||
|         except: | ||||
|             log.warning( | ||||
|                 "WhisperModel initialization failed, attempting download with local_files_only=False" | ||||
|             ) | ||||
|             whisper_kwargs["local_files_only"] = False | ||||
|             model = WhisperModel(**whisper_kwargs) | ||||
| 
 | ||||
|         segments, info = model.transcribe(file_path, beam_size=5) | ||||
|         log.info( | ||||
|  |  | |||
|  | @ -29,7 +29,13 @@ import base64 | |||
| import json | ||||
| import logging | ||||
| 
 | ||||
| from config import SRC_LOG_LEVELS, CACHE_DIR, AUTOMATIC1111_BASE_URL, COMFYUI_BASE_URL | ||||
| from config import ( | ||||
|     SRC_LOG_LEVELS, | ||||
|     CACHE_DIR, | ||||
|     ENABLE_IMAGE_GENERATION, | ||||
|     AUTOMATIC1111_BASE_URL, | ||||
|     COMFYUI_BASE_URL, | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| log = logging.getLogger(__name__) | ||||
|  | @ -48,7 +54,7 @@ app.add_middleware( | |||
| ) | ||||
| 
 | ||||
| app.state.ENGINE = "" | ||||
| app.state.ENABLED = False | ||||
| app.state.ENABLED = ENABLE_IMAGE_GENERATION | ||||
| 
 | ||||
| app.state.OPENAI_API_KEY = "" | ||||
| app.state.MODEL = "" | ||||
|  |  | |||
|  | @ -612,8 +612,13 @@ async def generate_embeddings( | |||
|     user=Depends(get_current_user), | ||||
| ): | ||||
|     if url_idx == None: | ||||
|         if form_data.model in app.state.MODELS: | ||||
|             url_idx = random.choice(app.state.MODELS[form_data.model]["urls"]) | ||||
|         model = form_data.model | ||||
| 
 | ||||
|         if ":" not in model: | ||||
|             model = f"{model}:latest" | ||||
| 
 | ||||
|         if model in app.state.MODELS: | ||||
|             url_idx = random.choice(app.state.MODELS[model]["urls"]) | ||||
|         else: | ||||
|             raise HTTPException( | ||||
|                 status_code=400, | ||||
|  | @ -672,8 +677,13 @@ async def generate_completion( | |||
| ): | ||||
| 
 | ||||
|     if url_idx == None: | ||||
|         if form_data.model in app.state.MODELS: | ||||
|             url_idx = random.choice(app.state.MODELS[form_data.model]["urls"]) | ||||
|         model = form_data.model | ||||
| 
 | ||||
|         if ":" not in model: | ||||
|             model = f"{model}:latest" | ||||
| 
 | ||||
|         if model in app.state.MODELS: | ||||
|             url_idx = random.choice(app.state.MODELS[model]["urls"]) | ||||
|         else: | ||||
|             raise HTTPException( | ||||
|                 status_code=400, | ||||
|  | @ -770,8 +780,13 @@ async def generate_chat_completion( | |||
| ): | ||||
| 
 | ||||
|     if url_idx == None: | ||||
|         if form_data.model in app.state.MODELS: | ||||
|             url_idx = random.choice(app.state.MODELS[form_data.model]["urls"]) | ||||
|         model = form_data.model | ||||
| 
 | ||||
|         if ":" not in model: | ||||
|             model = f"{model}:latest" | ||||
| 
 | ||||
|         if model in app.state.MODELS: | ||||
|             url_idx = random.choice(app.state.MODELS[model]["urls"]) | ||||
|         else: | ||||
|             raise HTTPException( | ||||
|                 status_code=400, | ||||
|  | @ -874,8 +889,13 @@ async def generate_openai_chat_completion( | |||
| ): | ||||
| 
 | ||||
|     if url_idx == None: | ||||
|         if form_data.model in app.state.MODELS: | ||||
|             url_idx = random.choice(app.state.MODELS[form_data.model]["urls"]) | ||||
|         model = form_data.model | ||||
| 
 | ||||
|         if ":" not in model: | ||||
|             model = f"{model}:latest" | ||||
| 
 | ||||
|         if model in app.state.MODELS: | ||||
|             url_idx = random.choice(app.state.MODELS[model]["urls"]) | ||||
|         else: | ||||
|             raise HTTPException( | ||||
|                 status_code=400, | ||||
|  |  | |||
|  | @ -413,7 +413,7 @@ RAG_EMBEDDING_MODEL_AUTO_UPDATE = ( | |||
| ) | ||||
| 
 | ||||
| 
 | ||||
| # device type embbeding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance | ||||
| # device type embedding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance | ||||
| USE_CUDA = os.environ.get("USE_CUDA_DOCKER", "false") | ||||
| 
 | ||||
| if USE_CUDA.lower() == "true": | ||||
|  | @ -450,11 +450,17 @@ Query: [query]""" | |||
| 
 | ||||
| WHISPER_MODEL = os.getenv("WHISPER_MODEL", "base") | ||||
| WHISPER_MODEL_DIR = os.getenv("WHISPER_MODEL_DIR", f"{CACHE_DIR}/whisper/models") | ||||
| WHISPER_MODEL_AUTO_UPDATE = ( | ||||
|     os.environ.get("WHISPER_MODEL_AUTO_UPDATE", "").lower() == "true" | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| #################################### | ||||
| # Images | ||||
| #################################### | ||||
| 
 | ||||
| ENABLE_IMAGE_GENERATION = ( | ||||
|     os.environ.get("ENABLE_IMAGE_GENERATION", "").lower() == "true" | ||||
| ) | ||||
| AUTOMATIC1111_BASE_URL = os.getenv("AUTOMATIC1111_BASE_URL", "") | ||||
| COMFYUI_BASE_URL = os.getenv("COMFYUI_BASE_URL", "") | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
| 	<head> | ||||
| 		<meta charset="utf-8" /> | ||||
| 		<link rel="icon" href="%sveltekit.assets%/favicon.png" /> | ||||
| 		<link rel="manifest" href="%sveltekit.assets%/manifest.json" /> | ||||
| 		<link rel="manifest" href="%sveltekit.assets%/manifest.json" crossorigin="use-credentials" /> | ||||
| 		<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> | ||||
| 		<meta name="robots" content="noindex,nofollow" /> | ||||
| 		<script> | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ | |||
| 	import { synthesizeOpenAISpeech } from '$lib/apis/openai'; | ||||
| 	import { imageGenerations } from '$lib/apis/images'; | ||||
| 	import { | ||||
| 		approximateToHumanReadable, | ||||
| 		extractSentences, | ||||
| 		revertSanitizedResponseContent, | ||||
| 		sanitizeResponseContent | ||||
|  | @ -122,7 +123,10 @@ | |||
|                     eval_count: ${message.info.eval_count ?? 'N/A'}<br/> | ||||
|                     eval_duration: ${ | ||||
| 											Math.round(((message.info.eval_duration ?? 0) / 1000000) * 100) / 100 ?? 'N/A' | ||||
| 										}ms</span>`, | ||||
| 										}ms<br/> | ||||
|                     approximate_total: ${approximateToHumanReadable( | ||||
| 											message.info.total_duration | ||||
| 										)}</span>`, | ||||
| 				allowHTML: true | ||||
| 			}); | ||||
| 		} | ||||
|  |  | |||
|  | @ -33,7 +33,7 @@ | |||
| 		: items; | ||||
| 
 | ||||
| 	const pullModelHandler = async () => { | ||||
| 		const sanitizedModelTag = searchValue.trim(); | ||||
| 		const sanitizedModelTag = searchValue.trim().replace(/^ollama\s+(run|pull)\s+/, ''); | ||||
| 
 | ||||
| 		console.log($MODEL_DOWNLOAD_POOL); | ||||
| 		if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]) { | ||||
|  |  | |||
|  | @ -139,7 +139,7 @@ | |||
| 	}; | ||||
| 
 | ||||
| 	const pullModelHandler = async () => { | ||||
| 		const sanitizedModelTag = modelTag.trim(); | ||||
| 		const sanitizedModelTag = modelTag.trim().replace(/^ollama\s+(run|pull)\s+/, ''); | ||||
| 		if (modelDownloadStatus[sanitizedModelTag]) { | ||||
| 			toast.error( | ||||
| 				$i18n.t(`Model '{{modelTag}}' is already in queue for downloading.`, { | ||||
|  |  | |||
							
								
								
									
										42
									
								
								src/lib/components/common/Pagination.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/lib/components/common/Pagination.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| <script lang="ts"> | ||||
| 	import { Pagination } from 'bits-ui'; | ||||
| 	import { createEventDispatcher } from 'svelte'; | ||||
| 
 | ||||
| 	import ChevronLeft from '../icons/ChevronLeft.svelte'; | ||||
| 	import ChevronRight from '../icons/ChevronRight.svelte'; | ||||
| 
 | ||||
| 	export let page = 0; | ||||
| 	export let count = 0; | ||||
| 	export let perPage = 20; | ||||
| </script> | ||||
| 
 | ||||
| <div class="flex justify-center"> | ||||
| 	<Pagination.Root bind:page {count} {perPage} let:pages> | ||||
| 		<div class="my-2 flex items-center"> | ||||
| 			<Pagination.PrevButton | ||||
| 				class="mr-[25px] inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 active:scale-98 disabled:cursor-not-allowed disabled:text-gray-400 dark:disabled:text-gray-700 hover:disabled:bg-transparent dark:hover:disabled:bg-transparent" | ||||
| 			> | ||||
| 				<ChevronLeft className="size-4" strokeWidth="2" /> | ||||
| 			</Pagination.PrevButton> | ||||
| 			<div class="flex items-center gap-2.5"> | ||||
| 				{#each pages as page (page.key)} | ||||
| 					{#if page.type === 'ellipsis'} | ||||
| 						<div class="text-sm font-medium text-foreground-alt">...</div> | ||||
| 					{:else} | ||||
| 						<Pagination.Page | ||||
| 							{page} | ||||
| 							class="inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 text-sm font-medium hover:bg-dark-10 active:scale-98 disabled:cursor-not-allowed disabled:opacity-50 hover:disabled:bg-transparent data-[selected]:bg-black data-[selected]:text-gray-100 data-[selected]:hover:bg-black dark:data-[selected]:bg-white dark:data-[selected]:text-gray-900 dark:data-[selected]:hover:bg-white" | ||||
| 						> | ||||
| 							{page.value} | ||||
| 						</Pagination.Page> | ||||
| 					{/if} | ||||
| 				{/each} | ||||
| 			</div> | ||||
| 			<Pagination.NextButton | ||||
| 				class="ml-[25px]  inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 active:scale-98 disabled:cursor-not-allowed disabled:text-gray-400 dark:disabled:text-gray-700 hover:disabled:bg-transparent dark:hover:disabled:bg-transparent" | ||||
| 			> | ||||
| 				<ChevronRight className="size-4" strokeWidth="2" /> | ||||
| 			</Pagination.NextButton> | ||||
| 		</div> | ||||
| 	</Pagination.Root> | ||||
| </div> | ||||
							
								
								
									
										15
									
								
								src/lib/components/icons/ChevronLeft.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/lib/components/icons/ChevronLeft.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| <script lang="ts"> | ||||
| 	export let className = 'w-4 h-4'; | ||||
| 	export let strokeWidth = '1.5'; | ||||
| </script> | ||||
| 
 | ||||
| <svg | ||||
| 	xmlns="http://www.w3.org/2000/svg" | ||||
| 	fill="none" | ||||
| 	viewBox="0 0 24 24" | ||||
| 	stroke-width={strokeWidth} | ||||
| 	stroke="currentColor" | ||||
| 	class={className} | ||||
| > | ||||
| 	<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" /> | ||||
| </svg> | ||||
							
								
								
									
										15
									
								
								src/lib/components/icons/ChevronRight.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/lib/components/icons/ChevronRight.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| <script lang="ts"> | ||||
| 	export let className = 'w-4 h-4'; | ||||
| 	export let strokeWidth = '1.5'; | ||||
| </script> | ||||
| 
 | ||||
| <svg | ||||
| 	xmlns="http://www.w3.org/2000/svg" | ||||
| 	fill="none" | ||||
| 	viewBox="0 0 24 24" | ||||
| 	stroke-width={strokeWidth} | ||||
| 	stroke="currentColor" | ||||
| 	class={className} | ||||
| > | ||||
| 	<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" /> | ||||
| </svg> | ||||
|  | @ -26,14 +26,14 @@ | |||
| 
 | ||||
| 	<div slot="content"> | ||||
| 		<DropdownMenu.Content | ||||
| 			class="w-full max-w-[130px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow" | ||||
| 			class="w-full max-w-[150px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-900 dark:text-white shadow" | ||||
| 			sideOffset={-2} | ||||
| 			side="bottom" | ||||
| 			align="start" | ||||
| 			transition={flyAndScale} | ||||
| 		> | ||||
| 			<DropdownMenu.Item | ||||
| 				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer" | ||||
| 				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer dark:hover:bg-gray-850 rounded-md" | ||||
| 				on:click={() => { | ||||
| 					renameHandler(); | ||||
| 				}} | ||||
|  | @ -43,7 +43,7 @@ | |||
| 			</DropdownMenu.Item> | ||||
| 
 | ||||
| 			<DropdownMenu.Item | ||||
| 				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer" | ||||
| 				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer dark:hover:bg-gray-850 rounded-md" | ||||
| 				on:click={() => { | ||||
| 					deleteHandler(); | ||||
| 				}} | ||||
|  |  | |||
|  | @ -55,9 +55,9 @@ | |||
| 	"Check for updates": "Kiểm tra cập nhật", | ||||
| 	"Checking for updates...": "Đang kiểm tra cập nhật...", | ||||
| 	"Choose a model before saving...": "Chọn mô hình trước khi lưu...", | ||||
| 	"Chunk Overlap": "Kích thước chồng lấn (overlap)", | ||||
| 	"Chunk Overlap": "Chồng lấn (overlap)", | ||||
| 	"Chunk Params": "Cài đặt số lượng ký tự cho khối ký tự (chunk)", | ||||
| 	"Chunk Size": "Kích thức khối (size)", | ||||
| 	"Chunk Size": "Kích thước khối (size)", | ||||
| 	"Click here for help.": "Bấm vào đây để được trợ giúp.", | ||||
| 	"Click here to check other modelfiles.": "Bấm vào đây để kiểm tra các tệp mô tả mô hình (modelfiles) khác.", | ||||
| 	"Click here to select": "Bấm vào đây để chọn", | ||||
|  | @ -65,7 +65,7 @@ | |||
| 	"click here.": "bấm vào đây.", | ||||
| 	"Click on the user role button to change a user's role.": "Bấm vào nút trong cột VAI TRÒ để thay đổi quyền của người sử dụng.", | ||||
| 	"Close": "Đóng", | ||||
| 	"Collection": "Bộ sưu tập", | ||||
| 	"Collection": "Tổng hợp mọi tài liệu", | ||||
| 	"Command": "Lệnh", | ||||
| 	"Confirm Password": "Xác nhận Mật khẩu", | ||||
| 	"Connections": "Kết nối", | ||||
|  | @ -76,7 +76,7 @@ | |||
| 	"Copy last response": "Sao chép phản hồi cuối cùng", | ||||
| 	"Copying to clipboard was successful!": "Sao chép vào clipboard thành công!", | ||||
| 	"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Tạo một cụm từ súc tích, 3-5 từ làm tiêu đề cho truy vấn sau, tuân thủ nghiêm ngặt giới hạn 3-5 từ và tránh sử dụng từ 'tiêu đề':", | ||||
| 	"Create a modelfile": "Tạo tệp mô tả mô hình", | ||||
| 	"Create a modelfile": "Tạo tệp mô tả cho mô hình", | ||||
| 	"Create Account": "Tạo Tài khoản", | ||||
| 	"Created at": "Được tạo vào lúc", | ||||
| 	"Created by": "Được tạo bởi", | ||||
|  | @ -347,7 +347,7 @@ | |||
| 	"Valid time units:": "Đơn vị thời gian hợp lệ:", | ||||
| 	"variable": "biến", | ||||
| 	"variable to have them replaced with clipboard content.": "biến để có chúng được thay thế bằng nội dung clipboard.", | ||||
| 	"Version": "Phiên bản", | ||||
| 	"Version": "Version", | ||||
| 	"Web": "Web", | ||||
| 	"WebUI Add-ons": "Tiện ích WebUI", | ||||
| 	"WebUI Settings": "Cài đặt WebUI", | ||||
|  |  | |||
|  | @ -493,4 +493,25 @@ export const templatePrompt = (template: string, prompt: string) => { | |||
| 	} | ||||
| 
 | ||||
| 	return template; | ||||
|    | ||||
| export const approximateToHumanReadable = (nanoseconds: number) => { | ||||
| 	const seconds = Math.floor((nanoseconds / 1e9) % 60); | ||||
| 	const minutes = Math.floor((nanoseconds / 6e10) % 60); | ||||
| 	const hours = Math.floor((nanoseconds / 3.6e12) % 24); | ||||
| 
 | ||||
| 	const results: string[] = []; | ||||
| 
 | ||||
| 	if (seconds >= 0) { | ||||
| 		results.push(`${seconds}s`); | ||||
| 	} | ||||
| 
 | ||||
| 	if (minutes > 0) { | ||||
| 		results.push(`${minutes}m`); | ||||
| 	} | ||||
| 
 | ||||
| 	if (hours > 0) { | ||||
| 		results.push(`${hours}h`); | ||||
| 	} | ||||
| 
 | ||||
| 	return results.reverse().join(' '); | ||||
| }; | ||||
|  |  | |||
|  | @ -106,11 +106,6 @@ | |||
| 				// IndexedDB Not Found | ||||
| 			} | ||||
| 
 | ||||
| 			console.log(); | ||||
| 
 | ||||
| 			await models.set(await getModels()); | ||||
| 			await tick(); | ||||
| 
 | ||||
| 			await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}')); | ||||
| 
 | ||||
| 			await modelfiles.set(await getModelfiles(localStorage.token)); | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
| 	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths'; | ||||
| 	import EditUserModal from '$lib/components/admin/EditUserModal.svelte'; | ||||
| 	import SettingsModal from '$lib/components/admin/SettingsModal.svelte'; | ||||
| 	import Pagination from '$lib/components/common/Pagination.svelte'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
|  | @ -21,6 +22,8 @@ | |||
| 	let search = ''; | ||||
| 	let selectedUser = null; | ||||
| 
 | ||||
| 	let page = 1; | ||||
| 
 | ||||
| 	let showSettingsModal = false; | ||||
| 	let showEditUserModal = false; | ||||
| 
 | ||||
|  | @ -159,7 +162,8 @@ | |||
| 										</tr> | ||||
| 									</thead> | ||||
| 									<tbody> | ||||
| 										{#each users.filter((user) => { | ||||
| 										{#each users | ||||
| 											.filter((user) => { | ||||
| 												if (search === '') { | ||||
| 													return true; | ||||
| 												} else { | ||||
|  | @ -167,7 +171,8 @@ | |||
| 													const query = search.toLowerCase(); | ||||
| 													return name.includes(query); | ||||
| 												} | ||||
| 										}) as user} | ||||
| 											}) | ||||
| 											.slice((page - 1) * 20, page * 20) as user} | ||||
| 											<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700 text-xs"> | ||||
| 												<td class="px-3 py-2 min-w-[7rem] w-28"> | ||||
| 													<button | ||||
|  | @ -270,6 +275,8 @@ | |||
| 							<div class=" text-gray-500 text-xs mt-2 text-right"> | ||||
| 								ⓘ {$i18n.t("Click on the user role button to change a user's role.")} | ||||
| 							</div> | ||||
| 
 | ||||
| 							<Pagination bind:page count={users.length} /> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</div> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy Jaeryang Baek
						Timothy Jaeryang Baek