enh: emoji folder icon

This commit is contained in:
Timothy Jaeryang Baek 2025-09-04 02:50:50 +04:00
parent e42ee34672
commit b70c0f36c0
10 changed files with 119 additions and 42 deletions

View File

@ -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)

View File

@ -10,6 +10,7 @@ import mimetypes
from open_webui.models.folders import (
FolderForm,
FolderUpdateForm,
FolderModel,
Folders,
)
@ -113,22 +114,24 @@ 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:
existing_folder = Folders.get_folder_by_parent_id_and_user_id_and_name(
folder.parent_id, user.id, form_data.name
)
if existing_folder and existing_folder.id != id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT("Folder already exists"),
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
)
if existing_folder and existing_folder.id != id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT("Folder already exists"),
)
try:
folder = Folders.update_folder_by_id_and_user_id(id, user.id, form_data)
return folder
except Exception as e:
log.exception(e)

View File

@ -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) => {

View File

@ -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}

View File

@ -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">
<Folder className="size-4.5" strokeWidth="2" />
</div>
<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" />
{/if}
</button>
</EmojiPicker>
<div class="text-3xl">
{folder.name}

View File

@ -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}

View File

@ -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}

View File

@ -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

View File

@ -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 open}
<ChevronDown className=" size-3" strokeWidth="2.5" />
{#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.5" strokeWidth="2.5" />
{:else}
<ChevronRight className=" size-3.5" strokeWidth="2.5" />
{/if}
</div>
{:else}
<ChevronRight className=" size-3" strokeWidth="2.5" />
<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>