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, |     UPLOAD_DIR, | ||||||
|     WHISPER_MODEL, |     WHISPER_MODEL, | ||||||
|     WHISPER_MODEL_DIR, |     WHISPER_MODEL_DIR, | ||||||
|  |     WHISPER_MODEL_AUTO_UPDATE, | ||||||
|     DEVICE_TYPE, |     DEVICE_TYPE, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -69,12 +70,24 @@ def transcribe( | ||||||
|             f.write(contents) |             f.write(contents) | ||||||
|             f.close() |             f.close() | ||||||
| 
 | 
 | ||||||
|         model = WhisperModel( |         whisper_kwargs = { | ||||||
|             WHISPER_MODEL, |             "model_size_or_path": WHISPER_MODEL, | ||||||
|             device=whisper_device_type, |             "device": whisper_device_type, | ||||||
|             compute_type="int8", |             "compute_type": "int8", | ||||||
|             download_root=WHISPER_MODEL_DIR, |             "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) |         segments, info = model.transcribe(file_path, beam_size=5) | ||||||
|         log.info( |         log.info( | ||||||
|  |  | ||||||
|  | @ -29,7 +29,13 @@ import base64 | ||||||
| import json | import json | ||||||
| import logging | 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__) | log = logging.getLogger(__name__) | ||||||
|  | @ -48,7 +54,7 @@ app.add_middleware( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| app.state.ENGINE = "" | app.state.ENGINE = "" | ||||||
| app.state.ENABLED = False | app.state.ENABLED = ENABLE_IMAGE_GENERATION | ||||||
| 
 | 
 | ||||||
| app.state.OPENAI_API_KEY = "" | app.state.OPENAI_API_KEY = "" | ||||||
| app.state.MODEL = "" | app.state.MODEL = "" | ||||||
|  |  | ||||||
|  | @ -612,8 +612,13 @@ async def generate_embeddings( | ||||||
|     user=Depends(get_current_user), |     user=Depends(get_current_user), | ||||||
| ): | ): | ||||||
|     if url_idx == None: |     if url_idx == None: | ||||||
|         if form_data.model in app.state.MODELS: |         model = form_data.model | ||||||
|             url_idx = random.choice(app.state.MODELS[form_data.model]["urls"]) | 
 | ||||||
|  |         if ":" not in model: | ||||||
|  |             model = f"{model}:latest" | ||||||
|  | 
 | ||||||
|  |         if model in app.state.MODELS: | ||||||
|  |             url_idx = random.choice(app.state.MODELS[model]["urls"]) | ||||||
|         else: |         else: | ||||||
|             raise HTTPException( |             raise HTTPException( | ||||||
|                 status_code=400, |                 status_code=400, | ||||||
|  | @ -672,8 +677,13 @@ async def generate_completion( | ||||||
| ): | ): | ||||||
| 
 | 
 | ||||||
|     if url_idx == None: |     if url_idx == None: | ||||||
|         if form_data.model in app.state.MODELS: |         model = form_data.model | ||||||
|             url_idx = random.choice(app.state.MODELS[form_data.model]["urls"]) | 
 | ||||||
|  |         if ":" not in model: | ||||||
|  |             model = f"{model}:latest" | ||||||
|  | 
 | ||||||
|  |         if model in app.state.MODELS: | ||||||
|  |             url_idx = random.choice(app.state.MODELS[model]["urls"]) | ||||||
|         else: |         else: | ||||||
|             raise HTTPException( |             raise HTTPException( | ||||||
|                 status_code=400, |                 status_code=400, | ||||||
|  | @ -770,8 +780,13 @@ async def generate_chat_completion( | ||||||
| ): | ): | ||||||
| 
 | 
 | ||||||
|     if url_idx == None: |     if url_idx == None: | ||||||
|         if form_data.model in app.state.MODELS: |         model = form_data.model | ||||||
|             url_idx = random.choice(app.state.MODELS[form_data.model]["urls"]) | 
 | ||||||
|  |         if ":" not in model: | ||||||
|  |             model = f"{model}:latest" | ||||||
|  | 
 | ||||||
|  |         if model in app.state.MODELS: | ||||||
|  |             url_idx = random.choice(app.state.MODELS[model]["urls"]) | ||||||
|         else: |         else: | ||||||
|             raise HTTPException( |             raise HTTPException( | ||||||
|                 status_code=400, |                 status_code=400, | ||||||
|  | @ -874,8 +889,13 @@ async def generate_openai_chat_completion( | ||||||
| ): | ): | ||||||
| 
 | 
 | ||||||
|     if url_idx == None: |     if url_idx == None: | ||||||
|         if form_data.model in app.state.MODELS: |         model = form_data.model | ||||||
|             url_idx = random.choice(app.state.MODELS[form_data.model]["urls"]) | 
 | ||||||
|  |         if ":" not in model: | ||||||
|  |             model = f"{model}:latest" | ||||||
|  | 
 | ||||||
|  |         if model in app.state.MODELS: | ||||||
|  |             url_idx = random.choice(app.state.MODELS[model]["urls"]) | ||||||
|         else: |         else: | ||||||
|             raise HTTPException( |             raise HTTPException( | ||||||
|                 status_code=400, |                 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") | USE_CUDA = os.environ.get("USE_CUDA_DOCKER", "false") | ||||||
| 
 | 
 | ||||||
| if USE_CUDA.lower() == "true": | if USE_CUDA.lower() == "true": | ||||||
|  | @ -450,11 +450,17 @@ Query: [query]""" | ||||||
| 
 | 
 | ||||||
| WHISPER_MODEL = os.getenv("WHISPER_MODEL", "base") | WHISPER_MODEL = os.getenv("WHISPER_MODEL", "base") | ||||||
| WHISPER_MODEL_DIR = os.getenv("WHISPER_MODEL_DIR", f"{CACHE_DIR}/whisper/models") | 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 | # Images | ||||||
| #################################### | #################################### | ||||||
| 
 | 
 | ||||||
|  | ENABLE_IMAGE_GENERATION = ( | ||||||
|  |     os.environ.get("ENABLE_IMAGE_GENERATION", "").lower() == "true" | ||||||
|  | ) | ||||||
| AUTOMATIC1111_BASE_URL = os.getenv("AUTOMATIC1111_BASE_URL", "") | AUTOMATIC1111_BASE_URL = os.getenv("AUTOMATIC1111_BASE_URL", "") | ||||||
| COMFYUI_BASE_URL = os.getenv("COMFYUI_BASE_URL", "") | COMFYUI_BASE_URL = os.getenv("COMFYUI_BASE_URL", "") | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| 	<head> | 	<head> | ||||||
| 		<meta charset="utf-8" /> | 		<meta charset="utf-8" /> | ||||||
| 		<link rel="icon" href="%sveltekit.assets%/favicon.png" /> | 		<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="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> | ||||||
| 		<meta name="robots" content="noindex,nofollow" /> | 		<meta name="robots" content="noindex,nofollow" /> | ||||||
| 		<script> | 		<script> | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ | ||||||
| 	import { synthesizeOpenAISpeech } from '$lib/apis/openai'; | 	import { synthesizeOpenAISpeech } from '$lib/apis/openai'; | ||||||
| 	import { imageGenerations } from '$lib/apis/images'; | 	import { imageGenerations } from '$lib/apis/images'; | ||||||
| 	import { | 	import { | ||||||
|  | 		approximateToHumanReadable, | ||||||
| 		extractSentences, | 		extractSentences, | ||||||
| 		revertSanitizedResponseContent, | 		revertSanitizedResponseContent, | ||||||
| 		sanitizeResponseContent | 		sanitizeResponseContent | ||||||
|  | @ -122,7 +123,10 @@ | ||||||
|                     eval_count: ${message.info.eval_count ?? 'N/A'}<br/> |                     eval_count: ${message.info.eval_count ?? 'N/A'}<br/> | ||||||
|                     eval_duration: ${ |                     eval_duration: ${ | ||||||
| 											Math.round(((message.info.eval_duration ?? 0) / 1000000) * 100) / 100 ?? 'N/A' | 											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 | 				allowHTML: true | ||||||
| 			}); | 			}); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -33,7 +33,7 @@ | ||||||
| 		: items; | 		: items; | ||||||
| 
 | 
 | ||||||
| 	const pullModelHandler = async () => { | 	const pullModelHandler = async () => { | ||||||
| 		const sanitizedModelTag = searchValue.trim(); | 		const sanitizedModelTag = searchValue.trim().replace(/^ollama\s+(run|pull)\s+/, ''); | ||||||
| 
 | 
 | ||||||
| 		console.log($MODEL_DOWNLOAD_POOL); | 		console.log($MODEL_DOWNLOAD_POOL); | ||||||
| 		if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]) { | 		if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]) { | ||||||
|  |  | ||||||
|  | @ -139,7 +139,7 @@ | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	const pullModelHandler = async () => { | 	const pullModelHandler = async () => { | ||||||
| 		const sanitizedModelTag = modelTag.trim(); | 		const sanitizedModelTag = modelTag.trim().replace(/^ollama\s+(run|pull)\s+/, ''); | ||||||
| 		if (modelDownloadStatus[sanitizedModelTag]) { | 		if (modelDownloadStatus[sanitizedModelTag]) { | ||||||
| 			toast.error( | 			toast.error( | ||||||
| 				$i18n.t(`Model '{{modelTag}}' is already in queue for downloading.`, { | 				$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"> | 	<div slot="content"> | ||||||
| 		<DropdownMenu.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} | 			sideOffset={-2} | ||||||
| 			side="bottom" | 			side="bottom" | ||||||
| 			align="start" | 			align="start" | ||||||
| 			transition={flyAndScale} | 			transition={flyAndScale} | ||||||
| 		> | 		> | ||||||
| 			<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={() => { | 				on:click={() => { | ||||||
| 					renameHandler(); | 					renameHandler(); | ||||||
| 				}} | 				}} | ||||||
|  | @ -43,7 +43,7 @@ | ||||||
| 			</DropdownMenu.Item> | 			</DropdownMenu.Item> | ||||||
| 
 | 
 | ||||||
| 			<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={() => { | 				on:click={() => { | ||||||
| 					deleteHandler(); | 					deleteHandler(); | ||||||
| 				}} | 				}} | ||||||
|  |  | ||||||
|  | @ -55,9 +55,9 @@ | ||||||
| 	"Check for updates": "Kiểm tra cập nhật", | 	"Check for updates": "Kiểm tra cập nhật", | ||||||
| 	"Checking for updates...": "Đang 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...", | 	"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 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 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 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", | 	"Click here to select": "Bấm vào đây để chọn", | ||||||
|  | @ -65,7 +65,7 @@ | ||||||
| 	"click here.": "bấm vào đây.", | 	"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.", | 	"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", | 	"Close": "Đóng", | ||||||
| 	"Collection": "Bộ sưu tập", | 	"Collection": "Tổng hợp mọi tài liệu", | ||||||
| 	"Command": "Lệnh", | 	"Command": "Lệnh", | ||||||
| 	"Confirm Password": "Xác nhận Mật khẩu", | 	"Confirm Password": "Xác nhận Mật khẩu", | ||||||
| 	"Connections": "Kết nối", | 	"Connections": "Kết nối", | ||||||
|  | @ -76,7 +76,7 @@ | ||||||
| 	"Copy last response": "Sao chép phản hồi cuối cùng", | 	"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!", | 	"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 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", | 	"Create Account": "Tạo Tài khoản", | ||||||
| 	"Created at": "Được tạo vào lúc", | 	"Created at": "Được tạo vào lúc", | ||||||
| 	"Created by": "Được tạo bởi", | 	"Created by": "Được tạo bởi", | ||||||
|  | @ -347,7 +347,7 @@ | ||||||
| 	"Valid time units:": "Đơn vị thời gian hợp lệ:", | 	"Valid time units:": "Đơn vị thời gian hợp lệ:", | ||||||
| 	"variable": "biến", | 	"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.", | 	"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", | 	"Web": "Web", | ||||||
| 	"WebUI Add-ons": "Tiện ích WebUI", | 	"WebUI Add-ons": "Tiện ích WebUI", | ||||||
| 	"WebUI Settings": "Cài đặt WebUI", | 	"WebUI Settings": "Cài đặt WebUI", | ||||||
|  |  | ||||||
|  | @ -493,4 +493,25 @@ export const templatePrompt = (template: string, prompt: string) => { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return template; | 	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 | 				// IndexedDB Not Found | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			console.log(); |  | ||||||
| 
 |  | ||||||
| 			await models.set(await getModels()); |  | ||||||
| 			await tick(); |  | ||||||
| 
 |  | ||||||
| 			await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}')); | 			await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}')); | ||||||
| 
 | 
 | ||||||
| 			await modelfiles.set(await getModelfiles(localStorage.token)); | 			await modelfiles.set(await getModelfiles(localStorage.token)); | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ | ||||||
| 	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths'; | 	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths'; | ||||||
| 	import EditUserModal from '$lib/components/admin/EditUserModal.svelte'; | 	import EditUserModal from '$lib/components/admin/EditUserModal.svelte'; | ||||||
| 	import SettingsModal from '$lib/components/admin/SettingsModal.svelte'; | 	import SettingsModal from '$lib/components/admin/SettingsModal.svelte'; | ||||||
|  | 	import Pagination from '$lib/components/common/Pagination.svelte'; | ||||||
| 
 | 
 | ||||||
| 	const i18n = getContext('i18n'); | 	const i18n = getContext('i18n'); | ||||||
| 
 | 
 | ||||||
|  | @ -21,6 +22,8 @@ | ||||||
| 	let search = ''; | 	let search = ''; | ||||||
| 	let selectedUser = null; | 	let selectedUser = null; | ||||||
| 
 | 
 | ||||||
|  | 	let page = 1; | ||||||
|  | 
 | ||||||
| 	let showSettingsModal = false; | 	let showSettingsModal = false; | ||||||
| 	let showEditUserModal = false; | 	let showEditUserModal = false; | ||||||
| 
 | 
 | ||||||
|  | @ -159,15 +162,17 @@ | ||||||
| 										</tr> | 										</tr> | ||||||
| 									</thead> | 									</thead> | ||||||
| 									<tbody> | 									<tbody> | ||||||
| 										{#each users.filter((user) => { | 										{#each users | ||||||
| 											if (search === '') { | 											.filter((user) => { | ||||||
| 												return true; | 												if (search === '') { | ||||||
| 											} else { | 													return true; | ||||||
| 												let name = user.name.toLowerCase(); | 												} else { | ||||||
| 												const query = search.toLowerCase(); | 													let name = user.name.toLowerCase(); | ||||||
| 												return name.includes(query); | 													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"> | 											<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"> | 												<td class="px-3 py-2 min-w-[7rem] w-28"> | ||||||
| 													<button | 													<button | ||||||
|  | @ -270,6 +275,8 @@ | ||||||
| 							<div class=" text-gray-500 text-xs mt-2 text-right"> | 							<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.")} | 								ⓘ {$i18n.t("Click on the user role button to change a user's role.")} | ||||||
| 							</div> | 							</div> | ||||||
|  | 
 | ||||||
|  | 							<Pagination bind:page count={users.length} /> | ||||||
| 						</div> | 						</div> | ||||||
| 					</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy Jaeryang Baek
						Timothy Jaeryang Baek