Spaces:
Runtime error
Runtime error
| <script lang="ts"> | |
| import { createEventDispatcher, getContext, onMount, tick } from "svelte"; | |
| import { goto } from "$app/navigation"; | |
| import { fade, slide } from "svelte/transition"; | |
| import { getUsage } from "$lib/apis"; | |
| import { getSessionUser, userSignOut } from "$lib/apis/auths"; | |
| import { | |
| showSettings, | |
| mobile, | |
| showSidebar, | |
| showShortcuts, | |
| user, | |
| config, | |
| settings, | |
| } from "$lib/stores"; | |
| import { REXPRO_API_BASE_URL } from "$lib/constants"; | |
| import Dropdown from "$lib/components/common/Dropdown.svelte"; | |
| import Tooltip from "$lib/components/common/Tooltip.svelte"; | |
| import ArchiveBox from "$lib/components/icons/ArchiveBox.svelte"; | |
| import QuestionMarkCircle from "$lib/components/icons/QuestionMarkCircle.svelte"; | |
| import Map from "$lib/components/icons/Map.svelte"; | |
| import Keyboard from "$lib/components/icons/Keyboard.svelte"; | |
| import ShortcutsModal from "$lib/components/chat/ShortcutsModal.svelte"; | |
| import Settings from "$lib/components/icons/Settings.svelte"; | |
| import Code from "$lib/components/icons/Code.svelte"; | |
| import UserGroup from "$lib/components/icons/UserGroup.svelte"; | |
| import SignOut from "$lib/components/icons/SignOut.svelte"; | |
| import FaceSmile from "$lib/components/icons/FaceSmile.svelte"; | |
| import UserStatusModal from "./UserStatusModal.svelte"; | |
| import Emoji from "$lib/components/common/Emoji.svelte"; | |
| import XMark from "$lib/components/icons/XMark.svelte"; | |
| import Note from "$lib/components/icons/Note.svelte"; | |
| import Pin from "$lib/components/icons/Pin.svelte"; | |
| import PinSlash from "$lib/components/icons/PinSlash.svelte"; | |
| import { updateUserStatus, updateUserSettings } from "$lib/apis/users"; | |
| import { toast } from "svelte-sonner"; | |
| const i18n = getContext("i18n"); | |
| export let show = false; | |
| export let role = ""; | |
| export let profile = false; | |
| export let help = false; | |
| export let className = "w-[240px]"; | |
| export let align = "end"; | |
| export let showActiveUsers = true; | |
| let showUserStatusModal = false; | |
| let shiftKey = false; | |
| const dispatch = createEventDispatcher(); | |
| const DEFAULT_PINNED_ITEMS = ["notes", "workspace"]; | |
| $: pinnedItems = $settings?.pinnedMenuItems ?? DEFAULT_PINNED_ITEMS; | |
| const isPinned = (id: string) => { | |
| return pinnedItems.includes(id); | |
| }; | |
| const togglePin = async (id: string) => { | |
| let updated; | |
| if (isPinned(id)) { | |
| updated = pinnedItems.filter((item) => item !== id); | |
| } else { | |
| updated = [...pinnedItems, id]; | |
| } | |
| await settings.set({ ...$settings, pinnedMenuItems: updated }); | |
| await updateUserSettings(localStorage.token, { ui: $settings }); | |
| }; | |
| let usage = null; | |
| const getUsageInfo = async () => { | |
| const res = await getUsage(localStorage.token).catch((error) => { | |
| console.error("Error fetching usage info:", error); | |
| }); | |
| if (res) { | |
| usage = res; | |
| } else { | |
| usage = null; | |
| } | |
| }; | |
| const handleDropdownChange = (state) => { | |
| dispatch("change", state); | |
| // Fetch usage info when dropdown opens, if user has permission | |
| if ( | |
| state && | |
| ($config?.features?.enable_public_active_users_count || | |
| role === "admin") | |
| ) { | |
| getUsageInfo(); | |
| } | |
| }; | |
| </script> | |
| <svelte:window | |
| on:keydown={(e) => { | |
| if (e.key === "Shift") shiftKey = true; | |
| }} | |
| on:keyup={(e) => { | |
| if (e.key === "Shift") shiftKey = false; | |
| }} | |
| /> | |
| <ShortcutsModal bind:show={$showShortcuts} /> | |
| <UserStatusModal | |
| bind:show={showUserStatusModal} | |
| onSave={async () => { | |
| user.set(await getSessionUser(localStorage.token)); | |
| }} | |
| /> | |
| <Dropdown bind:show onOpenChange={handleDropdownChange} {align}> | |
| <slot /> | |
| <div slot="content"> | |
| <div | |
| class="{className} rounded-2xl px-1 py-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg text-sm" | |
| > | |
| {#if profile} | |
| <div class=" flex gap-3.5 w-full p-2.5 items-center"> | |
| <div class=" items-center flex shrink-0"> | |
| <img | |
| src={`${REXPRO_API_BASE_URL}/users/${$user?.id}/profile/image`} | |
| class=" size-10 object-cover rounded-full" | |
| alt="profile" | |
| /> | |
| </div> | |
| <div class=" flex flex-col w-full flex-1"> | |
| <div class="font-medium line-clamp-1 pr-2"> | |
| {$user.name} | |
| </div> | |
| <div class=" flex items-center gap-2"> | |
| {#if $user?.is_active ?? true} | |
| <div> | |
| <span class="relative flex size-2"> | |
| <span | |
| class="relative inline-flex rounded-full size-2 bg-green-500" | |
| /> | |
| </span> | |
| </div> | |
| <span class="text-xs"> | |
| {$i18n.t("Active")} | |
| </span> | |
| {:else} | |
| <div> | |
| <span class="relative flex size-2"> | |
| <span | |
| class="relative inline-flex rounded-full size-2 bg-gray-500" | |
| /> | |
| </span> | |
| </div> | |
| <span class="text-xs"> {$i18n.t("Away")} </span> | |
| {/if} | |
| </div> | |
| </div> | |
| </div> | |
| {#if $user?.status_emoji || $user?.status_message} | |
| <div class="mx-1"> | |
| <button | |
| class="mb-1 w-full gap-2 px-2.5 py-1.5 rounded-xl bg-gray-50 dark:text-white dark:bg-gray-900/50 text-black transition text-xs flex items-center" | |
| type="button" | |
| on:click={() => { | |
| show = false; | |
| showUserStatusModal = true; | |
| }} | |
| > | |
| {#if $user?.status_emoji} | |
| <div class=" self-center shrink-0"> | |
| <Emoji | |
| className="size-4" | |
| shortCode={$user?.status_emoji} | |
| /> | |
| </div> | |
| {/if} | |
| <Tooltip | |
| content={$user?.status_message} | |
| className=" self-center line-clamp-2 flex-1 text-left" | |
| > | |
| {$user?.status_message} | |
| </Tooltip> | |
| <div class="self-start"> | |
| <Tooltip content={$i18n.t("Clear status")}> | |
| <button | |
| type="button" | |
| on:click={async (e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| e.stopImmediatePropagation(); | |
| const res = await updateUserStatus( | |
| localStorage.token, | |
| { | |
| status_emoji: "", | |
| status_message: "", | |
| }, | |
| ); | |
| if (res) { | |
| toast.success( | |
| $i18n.t( | |
| "Status cleared successfully", | |
| ), | |
| ); | |
| user.set( | |
| await getSessionUser( | |
| localStorage.token, | |
| ), | |
| ); | |
| } else { | |
| toast.error( | |
| $i18n.t( | |
| "Failed to clear status", | |
| ), | |
| ); | |
| } | |
| }} | |
| > | |
| <XMark | |
| className="size-4 opacity-50" | |
| strokeWidth="2" | |
| /> | |
| </button> | |
| </Tooltip> | |
| </div> | |
| </button> | |
| </div> | |
| {:else} | |
| <div class="mx-1"> | |
| <button | |
| class="mb-1 w-full px-3 py-1.5 gap-1 rounded-xl bg-gray-50 dark:text-white dark:bg-gray-900/50 text-black transition text-xs flex items-center justify-center" | |
| type="button" | |
| on:click={() => { | |
| show = false; | |
| showUserStatusModal = true; | |
| }} | |
| > | |
| <div class=" self-center"> | |
| <FaceSmile | |
| className="size-4" | |
| strokeWidth="1.5" | |
| /> | |
| </div> | |
| <div class=" self-center truncate"> | |
| {$i18n.t("Update your status")} | |
| </div> | |
| </button> | |
| </div> | |
| {/if} | |
| <hr | |
| class=" border-gray-50/30 dark:border-gray-800/30 my-1.5 p-0" | |
| /> | |
| {/if} | |
| <button | |
| class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none" | |
| type="button" | |
| on:click={async () => { | |
| show = false; | |
| await showSettings.set(true); | |
| if ($mobile) { | |
| await tick(); | |
| showSidebar.set(false); | |
| } | |
| }} | |
| > | |
| <div class=" self-center mr-3"> | |
| <Settings className="w-5 h-5" strokeWidth="1.5" /> | |
| </div> | |
| <div class=" self-center truncate">{$i18n.t("Settings")}</div> | |
| </button> | |
| {#if role === "admin"} | |
| <a | |
| href="/admin" | |
| draggable="false" | |
| class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none" | |
| on:click={async (e) => { | |
| if ( | |
| e.metaKey || | |
| e.ctrlKey || | |
| e.shiftKey || | |
| e.button === 1 | |
| ) { | |
| return; | |
| } | |
| e.preventDefault(); | |
| show = false; | |
| goto("/admin"); | |
| if ($mobile) { | |
| await tick(); | |
| showSidebar.set(false); | |
| } | |
| }} | |
| > | |
| <div class=" self-center mr-3"> | |
| <UserGroup className="w-5 h-5" strokeWidth="1.5" /> | |
| </div> | |
| <div class=" self-center truncate"> | |
| {$i18n.t("Admin Panel")} | |
| </div> | |
| </a> | |
| {/if} | |
| <button | |
| class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none" | |
| type="button" | |
| on:click={async () => { | |
| show = false; | |
| dispatch("show", "archived-chat"); | |
| if ($mobile) { | |
| await tick(); | |
| showSidebar.set(false); | |
| } | |
| }} | |
| > | |
| <div class=" self-center mr-3"> | |
| <ArchiveBox className="size-5" strokeWidth="1.5" /> | |
| </div> | |
| <div class=" self-center truncate"> | |
| {$i18n.t("Archived Chats")} | |
| </div> | |
| </button> | |
| <hr class=" border-gray-50/30 dark:border-gray-800/30 my-1 p-0" /> | |
| {#if $user?.role === "admin" || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools} | |
| <div class="flex items-center w-full"> | |
| <a | |
| href="/workspace" | |
| draggable="false" | |
| class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none" | |
| on:click={async (e) => { | |
| if ( | |
| e.metaKey || | |
| e.ctrlKey || | |
| e.shiftKey || | |
| e.button === 1 | |
| ) | |
| return; | |
| e.preventDefault(); | |
| show = false; | |
| goto("/workspace"); | |
| if ($mobile) { | |
| await tick(); | |
| showSidebar.set(false); | |
| } | |
| }} | |
| > | |
| <div class="self-center mr-3"> | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| stroke-width="1.5" | |
| stroke="currentColor" | |
| class="size-5" | |
| > | |
| <path | |
| stroke-linecap="round" | |
| stroke-linejoin="round" | |
| d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 0 0 2.25-2.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v2.25A2.25 2.25 0 0 0 6 10.5Zm0 9.75h2.25A2.25 2.25 0 0 0 10.5 18v-2.25a2.25 2.25 0 0 0-2.25-2.25H6a2.25 2.25 0 0 0-2.25 2.25V18A2.25 2.25 0 0 0 6 20.25Zm9.75-9.75H18a2.25 2.25 0 0 0 2.25-2.25V6A2.25 2.25 0 0 0 18 3.75h-2.25A2.25 2.25 0 0 0 13.5 6v2.25a2.25 2.25 0 0 0 2.25 2.25Z" | |
| /> | |
| </svg> | |
| </div> | |
| <div class="self-center truncate"> | |
| {$i18n.t("Workspace")} | |
| </div> | |
| </a> | |
| {#if shiftKey} | |
| <Tooltip | |
| content={isPinned("workspace") | |
| ? $i18n.t("Unpin from Sidebar") | |
| : $i18n.t("Pin to Sidebar")} | |
| > | |
| <button | |
| type="button" | |
| class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition" | |
| on:click|preventDefault|stopPropagation={() => | |
| togglePin("workspace")} | |
| > | |
| {#if isPinned("workspace")} | |
| <PinSlash | |
| className="size-3.5" | |
| strokeWidth="1.5" | |
| /> | |
| {:else} | |
| <Pin | |
| className="size-3.5" | |
| strokeWidth="1.5" | |
| /> | |
| {/if} | |
| </button> | |
| </Tooltip> | |
| {/if} | |
| </div> | |
| {/if} | |
| {#if ($config?.features?.enable_notes ?? false) && ($user?.role === "admin" || ($user?.permissions?.features?.notes ?? true))} | |
| <div class="flex items-center w-full"> | |
| <a | |
| href="/notes" | |
| draggable="false" | |
| class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none" | |
| on:click={async (e) => { | |
| if ( | |
| e.metaKey || | |
| e.ctrlKey || | |
| e.shiftKey || | |
| e.button === 1 | |
| ) | |
| return; | |
| e.preventDefault(); | |
| show = false; | |
| goto("/notes"); | |
| if ($mobile) { | |
| await tick(); | |
| showSidebar.set(false); | |
| } | |
| }} | |
| > | |
| <div class="self-center mr-3"> | |
| <Note className="size-5" strokeWidth="1.5" /> | |
| </div> | |
| <div class="self-center truncate"> | |
| {$i18n.t("Notes")} | |
| </div> | |
| </a> | |
| {#if shiftKey} | |
| <Tooltip | |
| content={isPinned("notes") | |
| ? $i18n.t("Unpin from Sidebar") | |
| : $i18n.t("Pin to Sidebar")} | |
| > | |
| <button | |
| type="button" | |
| class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition" | |
| on:click|preventDefault|stopPropagation={() => | |
| togglePin("notes")} | |
| > | |
| {#if isPinned("notes")} | |
| <PinSlash | |
| className="size-3.5" | |
| strokeWidth="1.5" | |
| /> | |
| {:else} | |
| <Pin | |
| className="size-3.5" | |
| strokeWidth="1.5" | |
| /> | |
| {/if} | |
| </button> | |
| </Tooltip> | |
| {/if} | |
| </div> | |
| {/if} | |
| {#if $config?.features?.enable_calendar && ($user?.role === "admin" || $user?.permissions?.features?.calendar)} | |
| <div class="flex items-center w-full"> | |
| <a | |
| href="/calendar" | |
| draggable="false" | |
| class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none" | |
| on:click={async (e) => { | |
| if ( | |
| e.metaKey || | |
| e.ctrlKey || | |
| e.shiftKey || | |
| e.button === 1 | |
| ) | |
| return; | |
| e.preventDefault(); | |
| show = false; | |
| goto("/calendar"); | |
| }} | |
| > | |
| <div class="self-center mr-3"> | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| stroke-width="1.5" | |
| stroke="currentColor" | |
| class="size-5" | |
| > | |
| <path | |
| stroke-linecap="round" | |
| stroke-linejoin="round" | |
| d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5" | |
| /> | |
| </svg> | |
| </div> | |
| <div class="self-center truncate"> | |
| {$i18n.t("Calendar")} | |
| </div> | |
| </a> | |
| {#if shiftKey} | |
| <Tooltip | |
| content={isPinned("calendar") | |
| ? $i18n.t("Unpin from Sidebar") | |
| : $i18n.t("Pin to Sidebar")} | |
| > | |
| <button | |
| type="button" | |
| class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition" | |
| on:click|preventDefault|stopPropagation={() => | |
| togglePin("calendar")} | |
| > | |
| {#if isPinned("calendar")} | |
| <PinSlash | |
| className="size-3.5" | |
| strokeWidth="1.5" | |
| /> | |
| {:else} | |
| <Pin | |
| className="size-3.5" | |
| strokeWidth="1.5" | |
| /> | |
| {/if} | |
| </button> | |
| </Tooltip> | |
| {/if} | |
| </div> | |
| {/if} | |
| {#if $config?.features?.enable_automations && ($user?.role === "admin" || $user?.permissions?.features?.automations)} | |
| <div class="flex items-center w-full"> | |
| <a | |
| href="/automations" | |
| draggable="false" | |
| class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none" | |
| on:click={async (e) => { | |
| if ( | |
| e.metaKey || | |
| e.ctrlKey || | |
| e.shiftKey || | |
| e.button === 1 | |
| ) | |
| return; | |
| e.preventDefault(); | |
| show = false; | |
| goto("/automations"); | |
| if ($mobile) { | |
| await tick(); | |
| showSidebar.set(false); | |
| } | |
| }} | |
| > | |
| <div class="self-center mr-3"> | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| stroke-width="1.5" | |
| stroke="currentColor" | |
| class="size-5" | |
| > | |
| <path | |
| stroke-linecap="round" | |
| stroke-linejoin="round" | |
| d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" | |
| /> | |
| </svg> | |
| </div> | |
| <div class="self-center truncate"> | |
| {$i18n.t("Automations")} | |
| </div> | |
| </a> | |
| {#if shiftKey} | |
| <Tooltip | |
| content={isPinned("automations") | |
| ? $i18n.t("Unpin from Sidebar") | |
| : $i18n.t("Pin to Sidebar")} | |
| > | |
| <button | |
| type="button" | |
| class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition" | |
| on:click|preventDefault|stopPropagation={() => | |
| togglePin("automations")} | |
| > | |
| {#if isPinned("automations")} | |
| <PinSlash | |
| className="size-3.5" | |
| strokeWidth="1.5" | |
| /> | |
| {:else} | |
| <Pin | |
| className="size-3.5" | |
| strokeWidth="1.5" | |
| /> | |
| {/if} | |
| </button> | |
| </Tooltip> | |
| {/if} | |
| </div> | |
| {/if} | |
| {#if role === "admin"} | |
| <div class="flex items-center w-full"> | |
| <a | |
| href="/playground" | |
| draggable="false" | |
| class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none" | |
| on:click={async (e) => { | |
| if ( | |
| e.metaKey || | |
| e.ctrlKey || | |
| e.shiftKey || | |
| e.button === 1 | |
| ) | |
| return; | |
| e.preventDefault(); | |
| show = false; | |
| goto("/playground"); | |
| if ($mobile) { | |
| await tick(); | |
| showSidebar.set(false); | |
| } | |
| }} | |
| > | |
| <div class="self-center mr-3"> | |
| <Code className="size-5" strokeWidth="1.5" /> | |
| </div> | |
| <div class="self-center truncate"> | |
| {$i18n.t("Playground")} | |
| </div> | |
| </a> | |
| {#if shiftKey} | |
| <Tooltip | |
| content={isPinned("playground") | |
| ? $i18n.t("Unpin from Sidebar") | |
| : $i18n.t("Pin to Sidebar")} | |
| > | |
| <button | |
| type="button" | |
| class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition" | |
| on:click|preventDefault|stopPropagation={() => | |
| togglePin("playground")} | |
| > | |
| {#if isPinned("playground")} | |
| <PinSlash | |
| className="size-3.5" | |
| strokeWidth="1.5" | |
| /> | |
| {:else} | |
| <Pin | |
| className="size-3.5" | |
| strokeWidth="1.5" | |
| /> | |
| {/if} | |
| </button> | |
| </Tooltip> | |
| {/if} | |
| </div> | |
| {/if} | |
| {#if help} | |
| <hr | |
| class=" border-gray-50/30 dark:border-gray-800/30 my-1 p-0" | |
| /> | |
| <!-- {$i18n.t('Help')} --> | |
| {#if $user?.role === "admin"} | |
| <a | |
| href="https://docs.openrexpro.com" | |
| target="_blank" | |
| draggable="false" | |
| class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none" | |
| id="chat-share-button" | |
| on:click={() => { | |
| show = false; | |
| }} | |
| > | |
| <div class=" self-center mr-3"> | |
| <QuestionMarkCircle className="size-5" /> | |
| </div> | |
| <div class=" self-center truncate"> | |
| {$i18n.t("Documentation")} | |
| </div> | |
| </a> | |
| {/if} | |
| <button | |
| class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none" | |
| type="button" | |
| id="chat-share-button" | |
| on:click={async () => { | |
| show = false; | |
| showShortcuts.set(!$showShortcuts); | |
| if ($mobile) { | |
| await tick(); | |
| showSidebar.set(false); | |
| } | |
| }} | |
| > | |
| <div class=" self-center mr-3"> | |
| <Keyboard className="size-5" /> | |
| </div> | |
| <div class=" self-center truncate"> | |
| {$i18n.t("Keyboard shortcuts")} | |
| </div> | |
| </button> | |
| {/if} | |
| <hr class=" border-gray-50/30 dark:border-gray-800/30 my-1 p-0" /> | |
| <button | |
| class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none" | |
| type="button" | |
| on:click={async () => { | |
| const res = await userSignOut(); | |
| user.set(null); | |
| localStorage.removeItem("token"); | |
| location.href = res?.redirect_url ?? "/auth"; | |
| show = false; | |
| }} | |
| > | |
| <div class=" self-center mr-3"> | |
| <SignOut className="w-5 h-5" strokeWidth="1.5" /> | |
| </div> | |
| <div class=" self-center truncate">{$i18n.t("Sign Out")}</div> | |
| </button> | |
| </div> | |
| </div> | |
| </Dropdown> | |