Spaces:
Runtime error
Runtime error
| <script lang="ts"> | |
| import { getContext } from "svelte"; | |
| import { toast } from "svelte-sonner"; | |
| import { | |
| REXPRO_NAME, | |
| banners, | |
| chatId, | |
| config, | |
| mobile, | |
| settings, | |
| showArchivedChats, | |
| showControls, | |
| showSidebar, | |
| temporaryChatEnabled, | |
| user, | |
| } from "$lib/stores"; | |
| import { slide } from "svelte/transition"; | |
| import { page } from "$app/stores"; | |
| import { goto } from "$app/navigation"; | |
| import ShareChatModal from "../chat/ShareChatModal.svelte"; | |
| import ModelSelector from "../chat/ModelSelector.svelte"; | |
| import Tooltip from "../common/Tooltip.svelte"; | |
| import Menu from "$lib/components/layout/Navbar/Menu.svelte"; | |
| import UserMenu from "$lib/components/layout/Sidebar/UserMenu.svelte"; | |
| import AdjustmentsHorizontal from "../icons/AdjustmentsHorizontal.svelte"; | |
| import PencilSquare from "../icons/PencilSquare.svelte"; | |
| import Banner from "../common/Banner.svelte"; | |
| import Sidebar from "../icons/Sidebar.svelte"; | |
| import ChatBubbleDotted from "../icons/ChatBubbleDotted.svelte"; | |
| import ChatBubbleDottedChecked from "../icons/ChatBubbleDottedChecked.svelte"; | |
| import EllipsisHorizontal from "../icons/EllipsisHorizontal.svelte"; | |
| import ChatPlus from "../icons/ChatPlus.svelte"; | |
| import ChatCheck from "../icons/ChatCheck.svelte"; | |
| import Knobs from "../icons/Knobs.svelte"; | |
| import { REXPRO_API_BASE_URL } from "$lib/constants"; | |
| const i18n = getContext("i18n"); | |
| export let initNewChat: Function; | |
| export let shareEnabled: boolean = false; | |
| export let scrollTop = 0; | |
| export let scrollToTop: (() => void) | null = null; | |
| export let chat; | |
| export let history; | |
| export let selectedModels; | |
| export let showModelSelector = true; | |
| export let onSaveTempChat: () => {}; | |
| export let archiveChatHandler: (id: string) => void; | |
| export let deleteChatHandler: (id: string) => void; | |
| export let moveChatHandler: (id: string, folderId: string) => void; | |
| let closedBannerIds = []; | |
| const getDismissedBannerIds = (): string[] => { | |
| try { | |
| return JSON.parse( | |
| localStorage.getItem("dismissedBannerIds") ?? "[]", | |
| ); | |
| } catch { | |
| return []; | |
| } | |
| }; | |
| let showShareChatModal = false; | |
| let showDownloadChatModal = false; | |
| </script> | |
| <ShareChatModal bind:show={showShareChatModal} chatId={$chatId} /> | |
| <button | |
| id="new-chat-button" | |
| class="hidden" | |
| on:click={() => { | |
| initNewChat(); | |
| }} | |
| aria-label="New Chat" | |
| /> | |
| <nav | |
| class="sticky top-0 z-30 w-full {chat?.id | |
| ? 'pt-0.5 pb-1' | |
| : 'pt-1 pb-1'} -mb-12 flex flex-col items-center drag-region" | |
| > | |
| <div class="flex items-center w-full pl-1.5 pr-1"> | |
| <div | |
| id="navbar-bg-gradient-to-b" | |
| class="{chat?.id | |
| ? 'visible' | |
| : 'invisible'} bg-linear-to-b via-40% to-97% from-white/90 via-white/50 to-transparent dark:from-gray-900/90 dark:via-gray-900/50 dark:to-transparent pointer-events-none absolute inset-0 -bottom-10 z-[-1]" | |
| ></div> | |
| <div | |
| class=" flex max-w-full w-full mx-auto px-1.5 md:px-2 pt-0.5 bg-transparent" | |
| > | |
| <div class="flex items-center w-full max-w-full"> | |
| {#if $mobile && !$showSidebar} | |
| <div | |
| class="-translate-x-0.5 mr-1 mt-1 self-start flex flex-none items-center text-gray-600 dark:text-gray-400" | |
| > | |
| <Tooltip | |
| content={$showSidebar | |
| ? $i18n.t("Close Sidebar") | |
| : $i18n.t("Open Sidebar")} | |
| > | |
| <button | |
| class=" cursor-pointer flex rounded-lg hover:bg-gray-100 dark:hover:bg-gray-850 transition" | |
| on:click={() => { | |
| showSidebar.set(!$showSidebar); | |
| }} | |
| > | |
| <div class=" self-center p-1.5"> | |
| <Sidebar /> | |
| </div> | |
| </button> | |
| </Tooltip> | |
| </div> | |
| {/if} | |
| <div | |
| class="flex-1 overflow-hidden max-w-full mt-0.5 py-0.5 | |
| {$showSidebar ? 'ml-1' : ''} | |
| " | |
| > | |
| {#if showModelSelector} | |
| <ModelSelector | |
| bind:selectedModels | |
| showSetDefault={!shareEnabled} | |
| /> | |
| {/if} | |
| </div> | |
| <div | |
| class="self-start flex flex-none items-center text-gray-600 dark:text-gray-400" | |
| > | |
| <!-- <div class="md:hidden flex self-center w-[1px] h-5 mx-2 bg-gray-300 dark:bg-stone-700" /> --> | |
| {#if $user?.role === "user" ? ($user?.permissions?.chat?.temporary ?? true) && !($user?.permissions?.chat?.temporary_enforced ?? false) : true} | |
| {#if !chat?.id} | |
| <Tooltip content={$i18n.t(`Temporary Chat`)}> | |
| <button | |
| class="flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition" | |
| id="temporary-chat-button" | |
| on:click={async () => { | |
| if ( | |
| ($settings?.temporaryChatByDefault ?? | |
| false) && | |
| $temporaryChatEnabled | |
| ) { | |
| // for proper initNewChat handling | |
| await temporaryChatEnabled.set( | |
| null, | |
| ); | |
| } else { | |
| await temporaryChatEnabled.set( | |
| !$temporaryChatEnabled, | |
| ); | |
| } | |
| if ($page.url.pathname !== "/") { | |
| await goto("/"); | |
| } | |
| // add 'temporary-chat=true' to the URL | |
| if ($temporaryChatEnabled) { | |
| window.history.replaceState( | |
| null, | |
| "", | |
| "?temporary-chat=true", | |
| ); | |
| } else { | |
| window.history.replaceState( | |
| null, | |
| "", | |
| location.pathname, | |
| ); | |
| } | |
| }} | |
| > | |
| <div class=" m-auto self-center"> | |
| {#if $temporaryChatEnabled} | |
| <ChatBubbleDottedChecked | |
| className=" size-4.5" | |
| strokeWidth="1.5" | |
| /> | |
| {:else} | |
| <ChatBubbleDotted | |
| className=" size-4.5" | |
| strokeWidth="1.5" | |
| /> | |
| {/if} | |
| </div> | |
| </button> | |
| </Tooltip> | |
| {:else if $temporaryChatEnabled} | |
| <Tooltip content={$i18n.t(`Save Chat`)}> | |
| <button | |
| class="flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition" | |
| id="save-temporary-chat-button" | |
| on:click={async () => { | |
| onSaveTempChat(); | |
| }} | |
| > | |
| <div class=" m-auto self-center"> | |
| <ChatCheck | |
| className=" size-4.5" | |
| strokeWidth="1.5" | |
| /> | |
| </div> | |
| </button> | |
| </Tooltip> | |
| {/if} | |
| {/if} | |
| {#if $mobile && !$temporaryChatEnabled && chat && chat.id} | |
| <Tooltip content={$i18n.t("New Chat")}> | |
| <button | |
| class=" flex {$showSidebar | |
| ? 'md:hidden' | |
| : ''} cursor-pointer px-2 py-2 rounded-xl text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-850 transition" | |
| on:click={() => { | |
| initNewChat(); | |
| }} | |
| aria-label="New Chat" | |
| > | |
| <div class=" m-auto self-center"> | |
| <ChatPlus | |
| className=" size-4.5" | |
| strokeWidth="1.5" | |
| /> | |
| </div> | |
| </button> | |
| </Tooltip> | |
| {/if} | |
| {#if shareEnabled && chat && (chat.id || $temporaryChatEnabled)} | |
| <Menu | |
| {chat} | |
| {shareEnabled} | |
| {scrollToTop} | |
| shareHandler={() => { | |
| showShareChatModal = !showShareChatModal; | |
| }} | |
| archiveChatHandler={() => { | |
| archiveChatHandler(chat.id); | |
| }} | |
| deleteChatHandler={() => { | |
| deleteChatHandler(chat.id); | |
| }} | |
| {moveChatHandler} | |
| > | |
| <button | |
| class="flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition" | |
| id="chat-context-menu-button" | |
| > | |
| <div class=" m-auto self-center"> | |
| <EllipsisHorizontal | |
| className=" size-5" | |
| strokeWidth="1.5" | |
| /> | |
| </div> | |
| </button> | |
| </Menu> | |
| {/if} | |
| {#if $user?.role === "admin" || ($user?.permissions.chat?.controls ?? true)} | |
| <Tooltip content={$i18n.t("Controls")}> | |
| <button | |
| class=" flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition" | |
| on:click={async () => { | |
| await showControls.set(!$showControls); | |
| }} | |
| aria-label="Controls" | |
| > | |
| <div class=" m-auto self-center"> | |
| <Knobs | |
| className=" size-5" | |
| strokeWidth="1" | |
| /> | |
| </div> | |
| </button> | |
| </Tooltip> | |
| {/if} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {#if $temporaryChatEnabled && ($chatId ?? "").startsWith("local:")} | |
| <div class=" w-full z-30 text-center"> | |
| <div class="text-xs text-gray-500">{$i18n.t("Temporary Chat")}</div> | |
| </div> | |
| {/if} | |
| <div class="absolute top-[100%] left-0 right-0 h-fit"> | |
| {#if !history.currentId && !$chatId && ($banners.length > 0 || ($config?.license_metadata?.type ?? null) === "trial" || (($config?.license_metadata?.seats ?? null) !== null && $config?.user_count > $config?.license_metadata?.seats))} | |
| <div class=" w-full z-30"> | |
| <div class=" flex flex-col gap-1 w-full"> | |
| {#if ($config?.license_metadata?.type ?? null) === "trial"} | |
| <Banner | |
| banner={{ | |
| type: "info", | |
| title: "Trial License", | |
| content: $i18n.t( | |
| "You are currently using a trial license. Please contact support to upgrade your license.", | |
| ), | |
| }} | |
| /> | |
| {/if} | |
| {#if ($config?.license_metadata?.seats ?? null) !== null && $config?.user_count > $config?.license_metadata?.seats} | |
| <Banner | |
| banner={{ | |
| type: "error", | |
| title: "License Error", | |
| content: $i18n.t( | |
| "Exceeded the number of seats in your license. Please contact support to increase the number of seats.", | |
| ), | |
| }} | |
| /> | |
| {/if} | |
| {#each $banners.filter((b) => ![...getDismissedBannerIds(), ...closedBannerIds].includes(b.id)) as banner (banner.id)} | |
| <Banner | |
| {banner} | |
| on:dismiss={(e) => { | |
| const bannerId = e.detail; | |
| if (banner.dismissible) { | |
| localStorage.setItem( | |
| "dismissedBannerIds", | |
| JSON.stringify( | |
| [ | |
| bannerId, | |
| ...getDismissedBannerIds(), | |
| ].filter((id) => | |
| $banners.find( | |
| (b) => b.id === id, | |
| ), | |
| ), | |
| ), | |
| ); | |
| } else { | |
| closedBannerIds = [ | |
| ...closedBannerIds, | |
| bannerId, | |
| ]; | |
| } | |
| }} | |
| /> | |
| {/each} | |
| </div> | |
| </div> | |
| {/if} | |
| </div> | |
| </nav> | |