enh: emoji folder icon
This commit is contained in:
		
							parent
							
								
									e42ee34672
								
							
						
					
					
						commit
						b70c0f36c0
					
				|  | @ -58,6 +58,14 @@ class FolderModel(BaseModel): | |||
| class FolderForm(BaseModel): | ||||
|     name: str | ||||
|     data: Optional[dict] = None | ||||
|     meta: Optional[dict] = None | ||||
|     model_config = ConfigDict(extra="allow") | ||||
| 
 | ||||
| 
 | ||||
| class FolderUpdateForm(BaseModel): | ||||
|     name: Optional[str] = None | ||||
|     data: Optional[dict] = None | ||||
|     meta: Optional[dict] = None | ||||
|     model_config = ConfigDict(extra="allow") | ||||
| 
 | ||||
| 
 | ||||
|  | @ -191,7 +199,7 @@ class FolderTable: | |||
|             return | ||||
| 
 | ||||
|     def update_folder_by_id_and_user_id( | ||||
|         self, id: str, user_id: str, form_data: FolderForm | ||||
|         self, id: str, user_id: str, form_data: FolderUpdateForm | ||||
|     ) -> Optional[FolderModel]: | ||||
|         try: | ||||
|             with get_db() as db: | ||||
|  | @ -222,8 +230,13 @@ class FolderTable: | |||
|                         **form_data["data"], | ||||
|                     } | ||||
| 
 | ||||
|                 folder.updated_at = int(time.time()) | ||||
|                 if "meta" in form_data: | ||||
|                     folder.meta = { | ||||
|                         **(folder.meta or {}), | ||||
|                         **form_data["meta"], | ||||
|                     } | ||||
| 
 | ||||
|                 folder.updated_at = int(time.time()) | ||||
|                 db.commit() | ||||
| 
 | ||||
|                 return FolderModel.model_validate(folder) | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import mimetypes | |||
| 
 | ||||
| from open_webui.models.folders import ( | ||||
|     FolderForm, | ||||
|     FolderUpdateForm, | ||||
|     FolderModel, | ||||
|     Folders, | ||||
| ) | ||||
|  | @ -113,10 +114,13 @@ async def get_folder_by_id(id: str, user=Depends(get_verified_user)): | |||
| 
 | ||||
| @router.post("/{id}/update") | ||||
| async def update_folder_name_by_id( | ||||
|     id: str, form_data: FolderForm, user=Depends(get_verified_user) | ||||
|     id: str, form_data: FolderUpdateForm, user=Depends(get_verified_user) | ||||
| ): | ||||
|     folder = Folders.get_folder_by_id_and_user_id(id, user.id) | ||||
|     if folder: | ||||
| 
 | ||||
|         if form_data.name is not None: | ||||
|             # Check if folder with same name exists | ||||
|             existing_folder = Folders.get_folder_by_parent_id_and_user_id_and_name( | ||||
|                 folder.parent_id, user.id, form_data.name | ||||
|             ) | ||||
|  | @ -128,7 +132,6 @@ async def update_folder_name_by_id( | |||
| 
 | ||||
|         try: | ||||
|             folder = Folders.update_folder_by_id_and_user_id(id, user.id, form_data) | ||||
| 
 | ||||
|             return folder | ||||
|         except Exception as e: | ||||
|             log.exception(e) | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| import { WEBUI_API_BASE_URL } from '$lib/constants'; | ||||
| 
 | ||||
| type FolderForm = { | ||||
| 	name: string; | ||||
| 	name?: string; | ||||
| 	data?: Record<string, any>; | ||||
| 	meta?: Record<string, any>; | ||||
| }; | ||||
| 
 | ||||
| export const createNewFolder = async (token: string, folderForm: FolderForm) => { | ||||
|  |  | |||
|  | @ -30,9 +30,10 @@ | |||
| 	import ProfilePreview from './Message/ProfilePreview.svelte'; | ||||
| 	import ChatBubbleOvalEllipsis from '$lib/components/icons/ChatBubble.svelte'; | ||||
| 	import FaceSmile from '$lib/components/icons/FaceSmile.svelte'; | ||||
| 	import ReactionPicker from './Message/ReactionPicker.svelte'; | ||||
| 	import EmojiPicker from '$lib/components/common/EmojiPicker.svelte'; | ||||
| 	import ChevronRight from '$lib/components/icons/ChevronRight.svelte'; | ||||
| 	import { formatDate } from '$lib/utils'; | ||||
| 	import Emoji from '$lib/components/common/Emoji.svelte'; | ||||
| 
 | ||||
| 	export let message; | ||||
| 	export let showUserProfile = true; | ||||
|  | @ -74,7 +75,7 @@ | |||
| 				<div | ||||
| 					class="flex gap-1 rounded-lg bg-white dark:bg-gray-850 shadow-md p-0.5 border border-gray-100 dark:border-gray-850" | ||||
| 				> | ||||
| 					<ReactionPicker | ||||
| 					<EmojiPicker | ||||
| 						onClose={() => (showButtons = false)} | ||||
| 						onSubmit={(name) => { | ||||
| 							showButtons = false; | ||||
|  | @ -91,7 +92,7 @@ | |||
| 								<FaceSmile /> | ||||
| 							</button> | ||||
| 						</Tooltip> | ||||
| 					</ReactionPicker> | ||||
| 					</EmojiPicker> | ||||
| 
 | ||||
| 					{#if !thread} | ||||
| 						<Tooltip content={$i18n.t('Reply in Thread')}> | ||||
|  | @ -275,20 +276,7 @@ | |||
| 												onReaction(reaction.name); | ||||
| 											}} | ||||
| 										> | ||||
| 											{#if $shortCodesToEmojis[reaction.name]} | ||||
| 												<img | ||||
| 													src="{WEBUI_BASE_URL}/assets/emojis/{$shortCodesToEmojis[ | ||||
| 														reaction.name | ||||
| 													].toLowerCase()}.svg" | ||||
| 													alt={reaction.name} | ||||
| 													class=" size-4" | ||||
| 													loading="lazy" | ||||
| 												/> | ||||
| 											{:else} | ||||
| 												<div> | ||||
| 													{reaction.name} | ||||
| 												</div> | ||||
| 											{/if} | ||||
| 											<Emoji shortCode={reaction.name} /> | ||||
| 
 | ||||
| 											{#if reaction.user_ids.length > 0} | ||||
| 												<div class="text-xs font-medium text-gray-500 dark:text-gray-400"> | ||||
|  | @ -299,7 +287,7 @@ | |||
| 									</Tooltip> | ||||
| 								{/each} | ||||
| 
 | ||||
| 								<ReactionPicker | ||||
| 								<EmojiPicker | ||||
| 									onSubmit={(name) => { | ||||
| 										onReaction(name); | ||||
| 									}} | ||||
|  | @ -311,7 +299,7 @@ | |||
| 											<FaceSmile /> | ||||
| 										</div> | ||||
| 									</Tooltip> | ||||
| 								</ReactionPicker> | ||||
| 								</EmojiPicker> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					{/if} | ||||
|  |  | |||
|  | @ -21,6 +21,8 @@ | |||
| 	import FolderMenu from '$lib/components/layout/Sidebar/Folders/FolderMenu.svelte'; | ||||
| 	import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte'; | ||||
| 	import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; | ||||
| 	import Emoji from '$lib/components/common/Emoji.svelte'; | ||||
| 	import EmojiPicker from '$lib/components/common/EmojiPicker.svelte'; | ||||
| 
 | ||||
| 	export let folder = null; | ||||
| 
 | ||||
|  | @ -63,6 +65,25 @@ | |||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	const updateIconHandler = async (iconName) => { | ||||
| 		const res = await updateFolderById(localStorage.token, folder.id, { | ||||
| 			meta: { | ||||
| 				icon: iconName | ||||
| 			} | ||||
| 		}).catch((error) => { | ||||
| 			toast.error(`${error}`); | ||||
| 			return null; | ||||
| 		}); | ||||
| 
 | ||||
| 		if (res) { | ||||
| 			folder.meta = { ...folder.meta, icon: iconName }; | ||||
| 
 | ||||
| 			toast.success($i18n.t('Folder updated successfully')); | ||||
| 			selectedFolder.set(folder); | ||||
| 			onUpdate(folder); | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	const deleteHandler = async () => { | ||||
| 		const res = await deleteFolderById(localStorage.token, folder.id).catch((error) => { | ||||
| 			toast.error(`${error}`); | ||||
|  | @ -116,9 +137,23 @@ | |||
| 
 | ||||
| 	<div class="mb-3 px-6 @md:max-w-3xl justify-between w-full flex relative group items-center"> | ||||
| 		<div class="text-center flex gap-3.5 items-center"> | ||||
| 			<div class=" rounded-full bg-gray-50 dark:bg-gray-800 p-3 w-fit"> | ||||
| 			<EmojiPicker | ||||
| 				onClose={() => {}} | ||||
| 				onSubmit={(name) => { | ||||
| 					console.log(name); | ||||
| 					updateIconHandler(name); | ||||
| 				}} | ||||
| 			> | ||||
| 				<button | ||||
| 					class=" rounded-full bg-gray-50 dark:bg-gray-800 size-11 flex justify-center items-center" | ||||
| 				> | ||||
| 					{#if folder?.meta?.icon} | ||||
| 						<Emoji className="size-6" shortCode={folder.meta.icon} /> | ||||
| 					{:else} | ||||
| 						<Folder className="size-4.5" strokeWidth="2" /> | ||||
| 			</div> | ||||
| 					{/if} | ||||
| 				</button> | ||||
| 			</EmojiPicker> | ||||
| 
 | ||||
| 			<div class="text-3xl"> | ||||
| 				{folder.name} | ||||
|  |  | |||
|  | @ -0,0 +1,20 @@ | |||
| <script> | ||||
| 	import { WEBUI_BASE_URL } from '$lib/constants'; | ||||
| 	import { shortCodesToEmojis } from '$lib/stores'; | ||||
| 
 | ||||
| 	export let shortCode; | ||||
| 	export let className = 'size-4'; | ||||
| </script> | ||||
| 
 | ||||
| {#if $shortCodesToEmojis[shortCode]} | ||||
| 	<img | ||||
| 		src="{WEBUI_BASE_URL}/assets/emojis/{$shortCodesToEmojis[shortCode].toLowerCase()}.svg" | ||||
| 		alt={shortCode} | ||||
| 		class={className} | ||||
| 		loading="lazy" | ||||
| 	/> | ||||
| {:else} | ||||
| 	<div> | ||||
| 		{shortCode} | ||||
| 	</div> | ||||
| {/if} | ||||
|  | @ -142,11 +142,11 @@ | |||
| 			> | ||||
| 				<button class="w-full py-1.5 pl-2 flex items-center gap-1.5 text-xs font-medium"> | ||||
| 					{#if chevron} | ||||
| 						<div class="text-gray-300 dark:text-gray-600"> | ||||
| 						<div class="text-gray-300 dark:text-gray-600 p-[1px]"> | ||||
| 							{#if open} | ||||
| 								<ChevronDown className=" size-3" strokeWidth="2.5" /> | ||||
| 								<ChevronDown className=" size-3.5" strokeWidth="2.5" /> | ||||
| 							{:else} | ||||
| 								<ChevronRight className=" size-3" strokeWidth="2.5" /> | ||||
| 								<ChevronRight className=" size-3.5" strokeWidth="2.5" /> | ||||
| 							{/if} | ||||
| 						</div> | ||||
| 					{/if} | ||||
|  |  | |||
|  | @ -695,7 +695,7 @@ | |||
| 			? `ml-[4.5rem] md:ml-0 ` | ||||
| 			: ' transition-all duration-300 '} shrink-0 text-gray-900 dark:text-gray-200 text-sm fixed top-0 left-0 overflow-x-hidden | ||||
|         " | ||||
| 		transition:slide={{ duration: 200, axis: 'x' }} | ||||
| 		transition:slide={{ duration: 250, axis: 'x' }} | ||||
| 		data-state={$showSidebar} | ||||
| 	> | ||||
| 		<div | ||||
|  |  | |||
|  | @ -38,6 +38,7 @@ | |||
| 	import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; | ||||
| 	import FolderModal from './Folders/FolderModal.svelte'; | ||||
| 	import { goto } from '$app/navigation'; | ||||
| 	import Emoji from '$lib/components/common/Emoji.svelte'; | ||||
| 
 | ||||
| 	export let open = false; | ||||
| 
 | ||||
|  | @ -444,15 +445,31 @@ | |||
| 				}} | ||||
| 			> | ||||
| 				<button | ||||
| 					class="text-gray-300 dark:text-gray-600" | ||||
| 					class="text-gray-300 dark:text-gray-600 transition-all" | ||||
| 					on:click={(e) => { | ||||
| 						e.stopPropagation(); | ||||
| 					}} | ||||
| 				> | ||||
| 					{#if folders[folderId]?.meta?.icon} | ||||
| 						<div class="flex group-hover:hidden transition-all"> | ||||
| 							<Emoji className="size-4" shortCode={folders[folderId].meta.icon} /> | ||||
| 						</div> | ||||
| 
 | ||||
| 						<div class="hidden group-hover:flex transition-all p-[1px]"> | ||||
| 							{#if open} | ||||
| 						<ChevronDown className=" size-3" strokeWidth="2.5" /> | ||||
| 								<ChevronDown className=" size-3.5" strokeWidth="2.5" /> | ||||
| 							{:else} | ||||
| 						<ChevronRight className=" size-3" strokeWidth="2.5" /> | ||||
| 								<ChevronRight className=" size-3.5" strokeWidth="2.5" /> | ||||
| 							{/if} | ||||
| 						</div> | ||||
| 					{:else} | ||||
| 						<div class="p-[1px]"> | ||||
| 							{#if open} | ||||
| 								<ChevronDown className=" size-3.5" strokeWidth="2.5" /> | ||||
| 							{:else} | ||||
| 								<ChevronRight className=" size-3.5" strokeWidth="2.5" /> | ||||
| 							{/if} | ||||
| 						</div> | ||||
| 					{/if} | ||||
| 				</button> | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue