enh: emoji folder icon
This commit is contained in:
parent
e42ee34672
commit
b70c0f36c0
|
|
@ -58,6 +58,14 @@ class FolderModel(BaseModel):
|
||||||
class FolderForm(BaseModel):
|
class FolderForm(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
data: Optional[dict] = None
|
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")
|
model_config = ConfigDict(extra="allow")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -191,7 +199,7 @@ class FolderTable:
|
||||||
return
|
return
|
||||||
|
|
||||||
def update_folder_by_id_and_user_id(
|
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]:
|
) -> Optional[FolderModel]:
|
||||||
try:
|
try:
|
||||||
with get_db() as db:
|
with get_db() as db:
|
||||||
|
|
@ -222,8 +230,13 @@ class FolderTable:
|
||||||
**form_data["data"],
|
**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()
|
db.commit()
|
||||||
|
|
||||||
return FolderModel.model_validate(folder)
|
return FolderModel.model_validate(folder)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import mimetypes
|
||||||
|
|
||||||
from open_webui.models.folders import (
|
from open_webui.models.folders import (
|
||||||
FolderForm,
|
FolderForm,
|
||||||
|
FolderUpdateForm,
|
||||||
FolderModel,
|
FolderModel,
|
||||||
Folders,
|
Folders,
|
||||||
)
|
)
|
||||||
|
|
@ -113,10 +114,13 @@ async def get_folder_by_id(id: str, user=Depends(get_verified_user)):
|
||||||
|
|
||||||
@router.post("/{id}/update")
|
@router.post("/{id}/update")
|
||||||
async def update_folder_name_by_id(
|
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)
|
folder = Folders.get_folder_by_id_and_user_id(id, user.id)
|
||||||
if folder:
|
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(
|
existing_folder = Folders.get_folder_by_parent_id_and_user_id_and_name(
|
||||||
folder.parent_id, user.id, form_data.name
|
folder.parent_id, user.id, form_data.name
|
||||||
)
|
)
|
||||||
|
|
@ -128,7 +132,6 @@ async def update_folder_name_by_id(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
folder = Folders.update_folder_by_id_and_user_id(id, user.id, form_data)
|
folder = Folders.update_folder_by_id_and_user_id(id, user.id, form_data)
|
||||||
|
|
||||||
return folder
|
return folder
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(e)
|
log.exception(e)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||||
|
|
||||||
type FolderForm = {
|
type FolderForm = {
|
||||||
name: string;
|
name?: string;
|
||||||
data?: Record<string, any>;
|
data?: Record<string, any>;
|
||||||
|
meta?: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createNewFolder = async (token: string, folderForm: FolderForm) => {
|
export const createNewFolder = async (token: string, folderForm: FolderForm) => {
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,10 @@
|
||||||
import ProfilePreview from './Message/ProfilePreview.svelte';
|
import ProfilePreview from './Message/ProfilePreview.svelte';
|
||||||
import ChatBubbleOvalEllipsis from '$lib/components/icons/ChatBubble.svelte';
|
import ChatBubbleOvalEllipsis from '$lib/components/icons/ChatBubble.svelte';
|
||||||
import FaceSmile from '$lib/components/icons/FaceSmile.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 ChevronRight from '$lib/components/icons/ChevronRight.svelte';
|
||||||
import { formatDate } from '$lib/utils';
|
import { formatDate } from '$lib/utils';
|
||||||
|
import Emoji from '$lib/components/common/Emoji.svelte';
|
||||||
|
|
||||||
export let message;
|
export let message;
|
||||||
export let showUserProfile = true;
|
export let showUserProfile = true;
|
||||||
|
|
@ -74,7 +75,7 @@
|
||||||
<div
|
<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"
|
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)}
|
onClose={() => (showButtons = false)}
|
||||||
onSubmit={(name) => {
|
onSubmit={(name) => {
|
||||||
showButtons = false;
|
showButtons = false;
|
||||||
|
|
@ -91,7 +92,7 @@
|
||||||
<FaceSmile />
|
<FaceSmile />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ReactionPicker>
|
</EmojiPicker>
|
||||||
|
|
||||||
{#if !thread}
|
{#if !thread}
|
||||||
<Tooltip content={$i18n.t('Reply in Thread')}>
|
<Tooltip content={$i18n.t('Reply in Thread')}>
|
||||||
|
|
@ -275,20 +276,7 @@
|
||||||
onReaction(reaction.name);
|
onReaction(reaction.name);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#if $shortCodesToEmojis[reaction.name]}
|
<Emoji shortCode={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}
|
|
||||||
|
|
||||||
{#if reaction.user_ids.length > 0}
|
{#if reaction.user_ids.length > 0}
|
||||||
<div class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
<div class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||||
|
|
@ -299,7 +287,7 @@
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<ReactionPicker
|
<EmojiPicker
|
||||||
onSubmit={(name) => {
|
onSubmit={(name) => {
|
||||||
onReaction(name);
|
onReaction(name);
|
||||||
}}
|
}}
|
||||||
|
|
@ -311,7 +299,7 @@
|
||||||
<FaceSmile />
|
<FaceSmile />
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ReactionPicker>
|
</EmojiPicker>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@
|
||||||
import FolderMenu from '$lib/components/layout/Sidebar/Folders/FolderMenu.svelte';
|
import FolderMenu from '$lib/components/layout/Sidebar/Folders/FolderMenu.svelte';
|
||||||
import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte';
|
import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte';
|
||||||
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.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;
|
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 deleteHandler = async () => {
|
||||||
const res = await deleteFolderById(localStorage.token, folder.id).catch((error) => {
|
const res = await deleteFolderById(localStorage.token, folder.id).catch((error) => {
|
||||||
toast.error(`${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="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="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" />
|
<Folder className="size-4.5" strokeWidth="2" />
|
||||||
</div>
|
{/if}
|
||||||
|
</button>
|
||||||
|
</EmojiPicker>
|
||||||
|
|
||||||
<div class="text-3xl">
|
<div class="text-3xl">
|
||||||
{folder.name}
|
{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">
|
<button class="w-full py-1.5 pl-2 flex items-center gap-1.5 text-xs font-medium">
|
||||||
{#if chevron}
|
{#if chevron}
|
||||||
<div class="text-gray-300 dark:text-gray-600">
|
<div class="text-gray-300 dark:text-gray-600 p-[1px]">
|
||||||
{#if open}
|
{#if open}
|
||||||
<ChevronDown className=" size-3" strokeWidth="2.5" />
|
<ChevronDown className=" size-3.5" strokeWidth="2.5" />
|
||||||
{:else}
|
{:else}
|
||||||
<ChevronRight className=" size-3" strokeWidth="2.5" />
|
<ChevronRight className=" size-3.5" strokeWidth="2.5" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -695,7 +695,7 @@
|
||||||
? `ml-[4.5rem] md:ml-0 `
|
? `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-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}
|
data-state={$showSidebar}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@
|
||||||
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||||
import FolderModal from './Folders/FolderModal.svelte';
|
import FolderModal from './Folders/FolderModal.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import Emoji from '$lib/components/common/Emoji.svelte';
|
||||||
|
|
||||||
export let open = false;
|
export let open = false;
|
||||||
|
|
||||||
|
|
@ -444,15 +445,31 @@
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="text-gray-300 dark:text-gray-600"
|
class="text-gray-300 dark:text-gray-600 transition-all"
|
||||||
on:click={(e) => {
|
on:click={(e) => {
|
||||||
e.stopPropagation();
|
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}
|
{#if open}
|
||||||
<ChevronDown className=" size-3" strokeWidth="2.5" />
|
<ChevronDown className=" size-3.5" strokeWidth="2.5" />
|
||||||
{:else}
|
{: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}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue