enh: channel navbar

This commit is contained in:
Timothy Jaeryang Baek 2024-12-22 22:33:13 -07:00
parent 198bd49cc2
commit 74cacf8bf5
6 changed files with 317 additions and 30 deletions

View File

@ -70,6 +70,7 @@ class ChannelTable:
channel = ChannelModel(
**{
**form_data.model_dump(),
"name": form_data.name.lower(),
"id": str(uuid.uuid4()),
"user_id": user_id,
"created_at": int(time.time_ns()),

View File

@ -2,12 +2,13 @@
import { toast } from 'svelte-sonner';
import { onDestroy, onMount, tick } from 'svelte';
import { socket } from '$lib/stores';
import { showSidebar, socket } from '$lib/stores';
import { getChannelById, getChannelMessages, sendMessage } from '$lib/apis/channels';
import Messages from './Messages.svelte';
import MessageInput from './MessageInput.svelte';
import { goto } from '$app/navigation';
import Navbar from './Navbar.svelte';
export let id = '';
@ -95,35 +96,42 @@
});
</script>
<div class="h-full md:max-w-[calc(100%-260px)] w-full max-w-full flex flex-col">
<div
class=" pb-2.5 max-w-full z-10 scrollbar-hidden w-full h-full pt-6 flex-1 flex flex-col-reverse overflow-auto"
id="messages-container"
bind:this={messagesContainerElement}
on:scroll={(e) => {
scrollEnd = Math.abs(messagesContainerElement.scrollTop) <= 50;
}}
>
{#key id}
<Messages
{channel}
{messages}
{top}
onLoad={async () => {
page += 1;
<div
class="h-screen max-h-[100dvh] {$showSidebar
? 'md:max-w-[calc(100%-260px)]'
: ''} w-full max-w-full flex flex-col"
>
{#if channel}
<Navbar {channel} />
<div
class=" pb-2.5 max-w-full z-10 scrollbar-hidden w-full h-full pt-6 flex-1 flex flex-col-reverse overflow-auto"
id="messages-container"
bind:this={messagesContainerElement}
on:scroll={(e) => {
scrollEnd = Math.abs(messagesContainerElement.scrollTop) <= 50;
}}
>
{#key id}
<Messages
{channel}
{messages}
{top}
onLoad={async () => {
page += 1;
const newMessages = await getChannelMessages(localStorage.token, id, page);
const newMessages = await getChannelMessages(localStorage.token, id, page);
if (newMessages.length === 0) {
top = true;
return;
}
if (newMessages.length === 0) {
top = true;
return;
}
messages = [...messages, ...newMessages];
}}
/>
{/key}
</div>
messages = [...messages, ...newMessages];
}}
/>
{/key}
</div>
{/if}
<div class=" pb-[1rem]">
<MessageInput onSubmit={submitHandler} {scrollToBottom} {scrollEnd} />

View File

@ -0,0 +1,84 @@
<script lang="ts">
import { getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import { showArchivedChats, showSidebar, user } from '$lib/stores';
import { slide } from 'svelte/transition';
import { page } from '$app/stores';
import UserMenu from '$lib/components/layout/Sidebar/UserMenu.svelte';
import MenuLines from '../icons/MenuLines.svelte';
import PencilSquare from '../icons/PencilSquare.svelte';
const i18n = getContext('i18n');
export let channel;
</script>
<div class="sticky top-0 z-30 w-full px-1.5 py-1.5 -mb-8 flex items-center">
<div
class=" bg-gradient-to-b via-50% from-white via-white to-transparent dark:from-gray-900 dark:via-gray-900 dark:to-transparent pointer-events-none absolute inset-0 -bottom-7 z-[-1] blur"
></div>
<div class=" flex max-w-full w-full mx-auto px-1 pt-0.5 bg-transparent">
<div class="flex items-center w-full max-w-full">
<div
class="{$showSidebar
? 'md:hidden'
: ''} mr-1 self-start flex flex-none items-center text-gray-600 dark:text-gray-400"
>
<button
id="sidebar-toggle-button"
class="cursor-pointer px-2 py-2 flex rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition"
on:click={() => {
showSidebar.set(!$showSidebar);
}}
aria-label="Toggle Sidebar"
>
<div class=" m-auto self-center">
<MenuLines />
</div>
</button>
</div>
<div
class="flex-1 overflow-hidden max-w-full py-0.5
{$showSidebar ? 'ml-1' : ''}
"
>
<div class="line-clamp-1 capitalize font-medium font-primary text-lg">
{channel.name}
</div>
</div>
<div class="self-start flex flex-none items-center text-gray-600 dark:text-gray-400">
{#if $user !== undefined}
<UserMenu
className="max-w-[200px]"
role={$user.role}
on:show={(e) => {
if (e.detail === 'archived-chat') {
showArchivedChats.set(true);
}
}}
>
<button
class="select-none flex rounded-xl p-1.5 w-full hover:bg-gray-50 dark:hover:bg-gray-850 transition"
aria-label="User Menu"
>
<div class=" self-center">
<img
src={$user.profile_image_url}
class="size-6 object-cover rounded-full"
alt="User profile"
draggable="false"
/>
</div>
</button>
</UserMenu>
{/if}
</div>
</div>
</div>
</div>

View File

@ -70,15 +70,15 @@
generateMoACompletion,
stopTask
} from '$lib/apis';
import { getTools } from '$lib/apis/tools';
import Banner from '../common/Banner.svelte';
import MessageInput from '$lib/components/chat/MessageInput.svelte';
import Messages from '$lib/components/chat/Messages.svelte';
import Navbar from '$lib/components/layout/Navbar.svelte';
import Navbar from '$lib/components/chat/Navbar.svelte';
import ChatControls from './ChatControls.svelte';
import EventConfirmDialog from '../common/ConfirmDialog.svelte';
import Placeholder from './Placeholder.svelte';
import { getTools } from '$lib/apis/tools';
import NotificationToast from '../NotificationToast.svelte';
export let chatIdProp = '';

View File

@ -0,0 +1,194 @@
<script lang="ts">
import { getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import {
WEBUI_NAME,
chatId,
mobile,
settings,
showArchivedChats,
showControls,
showSidebar,
temporaryChatEnabled,
user
} from '$lib/stores';
import { slide } from 'svelte/transition';
import { page } from '$app/stores';
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 MenuLines from '../icons/MenuLines.svelte';
import AdjustmentsHorizontal from '../icons/AdjustmentsHorizontal.svelte';
import PencilSquare from '../icons/PencilSquare.svelte';
const i18n = getContext('i18n');
export let initNewChat: Function;
export let title: string = $WEBUI_NAME;
export let shareEnabled: boolean = false;
export let chat;
export let selectedModels;
export let showModelSelector = true;
let showShareChatModal = false;
let showDownloadChatModal = false;
</script>
<ShareChatModal bind:show={showShareChatModal} chatId={$chatId} />
<div class="sticky top-0 z-30 w-full px-1.5 py-1.5 -mb-8 flex items-center">
<div
class=" bg-gradient-to-b via-50% from-white via-white to-transparent dark:from-gray-900 dark:via-gray-900 dark:to-transparent pointer-events-none absolute inset-0 -bottom-7 z-[-1] blur"
></div>
<div class=" flex max-w-full w-full mx-auto px-1 pt-0.5 bg-transparent">
<div class="flex items-center w-full max-w-full">
<div
class="{$showSidebar
? 'md:hidden'
: ''} mr-1 self-start flex flex-none items-center text-gray-600 dark:text-gray-400"
>
<button
id="sidebar-toggle-button"
class="cursor-pointer px-2 py-2 flex rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition"
on:click={() => {
showSidebar.set(!$showSidebar);
}}
aria-label="Toggle Sidebar"
>
<div class=" m-auto self-center">
<MenuLines />
</div>
</button>
</div>
<div
class="flex-1 overflow-hidden max-w-full 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 shareEnabled && chat && (chat.id || $temporaryChatEnabled)}
<Menu
{chat}
{shareEnabled}
shareHandler={() => {
showShareChatModal = !showShareChatModal;
}}
downloadHandler={() => {
showDownloadChatModal = !showDownloadChatModal;
}}
>
<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">
<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 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM18.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z"
/>
</svg>
</div>
</button>
</Menu>
{:else if $mobile}
<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">
<AdjustmentsHorizontal className=" size-5" strokeWidth="0.5" />
</div>
</button>
</Tooltip>
{/if}
{#if !$mobile}
<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">
<AdjustmentsHorizontal className=" size-5" strokeWidth="0.5" />
</div>
</button>
</Tooltip>
{/if}
<Tooltip content={$i18n.t('New Chat')}>
<button
id="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">
<PencilSquare className=" size-5" strokeWidth="2" />
</div>
</button>
</Tooltip>
{#if $user !== undefined}
<UserMenu
className="max-w-[200px]"
role={$user.role}
on:show={(e) => {
if (e.detail === 'archived-chat') {
showArchivedChats.set(true);
}
}}
>
<button
class="select-none flex rounded-xl p-1.5 w-full hover:bg-gray-50 dark:hover:bg-gray-850 transition"
aria-label="User Menu"
>
<div class=" self-center">
<img
src={$user.profile_image_url}
class="size-6 object-cover rounded-full"
alt="User profile"
draggable="false"
/>
</div>
</button>
</UserMenu>
{/if}
</div>
</div>
</div>
</div>

View File

@ -17,7 +17,7 @@
let loading = false;
$: if (name) {
name = name.replace(/\s/g, '-');
name = name.replace(/\s/g, '-').toLocaleLowerCase();
}
const submitHandler = async () => {