move and improve sidebar
This commit is contained in:
parent
f83f15d875
commit
22a4d2a7f3
5 changed files with 280 additions and 236 deletions
253
src/lib/components/app-sidebar.svelte
Normal file
253
src/lib/components/app-sidebar.svelte
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
<script lang="ts">
|
||||
import { Sidebar, useSidebarControls } from '$lib/components/ui/sidebar';
|
||||
import { cmdOrCtrl } from '$lib/hooks/is-mac.svelte';
|
||||
import Tooltip from '$lib/components/ui/tooltip.svelte';
|
||||
import { cn } from '$lib/utils/utils.js';
|
||||
import { Avatar } from 'melt/components';
|
||||
import LoaderCircleIcon from '~icons/lucide/loader-circle';
|
||||
import PinOffIcon from '~icons/lucide/pin-off';
|
||||
import { useConvexClient } from 'convex-svelte';
|
||||
import { session } from '$lib/state/session.svelte';
|
||||
import { api } from '$lib/backend/convex/_generated/api';
|
||||
import { callModal } from './ui/modal/global-modal.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { useCachedQuery } from '$lib/cache/cached-query.svelte';
|
||||
import type { Doc, Id } from '$lib/backend/convex/_generated/dataModel';
|
||||
import PinIcon from '~icons/lucide/pin';
|
||||
import XIcon from '~icons/lucide/x';
|
||||
import { page } from '$app/state';
|
||||
import { Button } from './ui/button';
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
const controls = useSidebarControls();
|
||||
|
||||
async function togglePin(conversationId: string) {
|
||||
if (!session.current?.session.token) return;
|
||||
|
||||
await client.mutation(api.conversations.togglePin, {
|
||||
conversation_id: conversationId as Id<'conversations'>,
|
||||
session_token: session.current.session.token,
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteConversation(conversationId: string) {
|
||||
const res = await callModal({
|
||||
title: 'Delete conversation',
|
||||
description: 'Are you sure you want to delete this conversation?',
|
||||
actions: { cancel: 'outline', delete: 'destructive' },
|
||||
});
|
||||
|
||||
if (res !== 'delete') return;
|
||||
|
||||
if (!session.current?.session.token) return;
|
||||
|
||||
await client.mutation(api.conversations.remove, {
|
||||
conversation_id: conversationId as Id<'conversations'>,
|
||||
session_token: session.current.session.token,
|
||||
});
|
||||
await goto(`/chat`);
|
||||
}
|
||||
|
||||
const conversationsQuery = useCachedQuery(api.conversations.get, {
|
||||
session_token: session.current?.session.token ?? '',
|
||||
});
|
||||
|
||||
function groupConversationsByTime(conversations: Doc<'conversations'>[]) {
|
||||
const now = Date.now();
|
||||
const oneDay = 24 * 60 * 60 * 1000;
|
||||
const sevenDays = 7 * oneDay;
|
||||
const thirtyDays = 30 * oneDay;
|
||||
|
||||
const groups = {
|
||||
pinned: [] as Doc<'conversations'>[],
|
||||
today: [] as Doc<'conversations'>[],
|
||||
yesterday: [] as Doc<'conversations'>[],
|
||||
lastWeek: [] as Doc<'conversations'>[],
|
||||
lastMonth: [] as Doc<'conversations'>[],
|
||||
older: [] as Doc<'conversations'>[],
|
||||
};
|
||||
|
||||
conversations.forEach((conversation) => {
|
||||
// Pinned conversations go to pinned group regardless of time
|
||||
if (conversation.pinned) {
|
||||
groups.pinned.push(conversation);
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedAt = conversation.updated_at ?? 0;
|
||||
const timeDiff = now - updatedAt;
|
||||
|
||||
if (timeDiff < oneDay) {
|
||||
groups.today.push(conversation);
|
||||
} else if (timeDiff < 2 * oneDay) {
|
||||
groups.yesterday.push(conversation);
|
||||
} else if (timeDiff < sevenDays) {
|
||||
groups.lastWeek.push(conversation);
|
||||
} else if (timeDiff < thirtyDays) {
|
||||
groups.lastMonth.push(conversation);
|
||||
} else {
|
||||
groups.older.push(conversation);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort pinned conversations by updated_at (most recent first)
|
||||
groups.pinned.sort((a, b) => {
|
||||
const aTime = a.updated_at ?? 0;
|
||||
const bTime = b.updated_at ?? 0;
|
||||
return bTime - aTime;
|
||||
});
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
const groupedConversations = $derived(groupConversationsByTime(conversationsQuery.data ?? []));
|
||||
|
||||
const templateConversations = $derived([
|
||||
{ key: 'pinned', label: 'Pinned', conversations: groupedConversations.pinned, icon: PinIcon },
|
||||
{ key: 'today', label: 'Today', conversations: groupedConversations.today },
|
||||
{ key: 'yesterday', label: 'Yesterday', conversations: groupedConversations.yesterday },
|
||||
{ key: 'lastWeek', label: 'Last 7 days', conversations: groupedConversations.lastWeek },
|
||||
{ key: 'lastMonth', label: 'Last 30 days', conversations: groupedConversations.lastMonth },
|
||||
{ key: 'older', label: 'Older', conversations: groupedConversations.older },
|
||||
]);
|
||||
</script>
|
||||
|
||||
<Sidebar class="flex flex-col overflow-clip p-2">
|
||||
<div class="flex place-items-center justify-center py-2">
|
||||
<span class="text-center font-serif text-lg">Thom.chat</span>
|
||||
</div>
|
||||
<div class="mt-1 flex w-full px-2">
|
||||
<Tooltip>
|
||||
{#snippet trigger(tooltip)}
|
||||
<a
|
||||
href="/chat"
|
||||
class="border-reflect button-reflect bg-primary/20 hover:bg-primary/50 font-fake-proxima w-full rounded-lg px-4 py-2 text-center text-sm tracking-[-0.005em] duration-200"
|
||||
{...tooltip.trigger}
|
||||
onclick={controls.closeMobile}
|
||||
style="font-variation-settings: 'wght' 750"
|
||||
>
|
||||
New Chat
|
||||
</a>
|
||||
{/snippet}
|
||||
{cmdOrCtrl} + Shift + O
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="relative flex min-h-0 flex-1 shrink-0 flex-col overflow-clip">
|
||||
<div
|
||||
class="from-sidebar pointer-events-none absolute top-0 right-0 left-0 z-10 h-4 bg-gradient-to-b to-transparent"
|
||||
></div>
|
||||
<div class="flex flex-1 flex-col overflow-y-auto py-2">
|
||||
{#each templateConversations as group, index (group.key)}
|
||||
{@const IconComponent = group.icon}
|
||||
{#if group.conversations.length > 0}
|
||||
<div class="px-2 py-1" class:mt-2={index > 0}>
|
||||
<h3 class="text-heading text-xs font-medium">
|
||||
{#if IconComponent}
|
||||
<IconComponent class="inline size-3" />
|
||||
{/if}
|
||||
{group.label}
|
||||
</h3>
|
||||
</div>
|
||||
{#each group.conversations as conversation (conversation._id)}
|
||||
{@const isActive = page.params.id === conversation._id}
|
||||
<a
|
||||
href={`/chat/${conversation._id}`}
|
||||
onclick={controls.closeMobile}
|
||||
class="group w-full py-0.5 pr-2.5 text-left text-sm"
|
||||
>
|
||||
<div
|
||||
class={cn(
|
||||
'relative flex w-full items-center justify-between overflow-clip rounded-lg',
|
||||
{ 'bg-sidebar-accent': isActive, 'group-hover:bg-sidebar-accent': !isActive }
|
||||
)}
|
||||
>
|
||||
<p class="truncate rounded-lg py-2 pr-4 pl-3 whitespace-nowrap">
|
||||
<span>{conversation.title}</span>
|
||||
</p>
|
||||
<div class="pr-2">
|
||||
{#if conversation.generating}
|
||||
<div
|
||||
class="flex animate-[spin_0.75s_linear_infinite] place-items-center justify-center"
|
||||
>
|
||||
<LoaderCircleIcon class="size-4" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div
|
||||
class={[
|
||||
'pointer-events-none absolute inset-y-0.5 right-0 flex translate-x-full items-center gap-2 rounded-r-lg pr-2 pl-6 transition group-hover:pointer-events-auto group-hover:translate-0',
|
||||
'to-sidebar-accent via-sidebar-accent bg-gradient-to-r from-transparent from-10% via-21% ',
|
||||
]}
|
||||
>
|
||||
<Tooltip>
|
||||
{#snippet trigger(tooltip)}
|
||||
<button
|
||||
{...tooltip.trigger}
|
||||
class="hover:bg-muted rounded-md p-1"
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
togglePin(conversation._id);
|
||||
}}
|
||||
>
|
||||
{#if conversation.pinned}
|
||||
<PinOffIcon class="size-4" />
|
||||
{:else}
|
||||
<PinIcon class="size-4" />
|
||||
{/if}
|
||||
</button>
|
||||
{/snippet}
|
||||
{conversation.pinned ? 'Unpin thread' : 'Pin thread'}
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
{#snippet trigger(tooltip)}
|
||||
<button
|
||||
{...tooltip.trigger}
|
||||
class="hover:bg-muted rounded-md p-1"
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
deleteConversation(conversation._id);
|
||||
}}
|
||||
>
|
||||
<XIcon class="size-4" />
|
||||
</button>
|
||||
{/snippet}
|
||||
Delete thread
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div
|
||||
class="from-sidebar pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-4 bg-gradient-to-t to-transparent"
|
||||
></div>
|
||||
</div>
|
||||
<div class="py-2">
|
||||
{#if page.data.session !== null}
|
||||
<Button href="/account" variant="ghost" class="h-auto w-full justify-start">
|
||||
<Avatar src={page.data.session?.user.image ?? undefined}>
|
||||
{#snippet children(avatar)}
|
||||
<img {...avatar.image} alt="Your avatar" class="size-10 rounded-full" />
|
||||
<span {...avatar.fallback} class="size-10 rounded-full">
|
||||
{page.data.session?.user.name
|
||||
.split(' ')
|
||||
.map((name: string) => name[0]?.toUpperCase())
|
||||
.join('')}
|
||||
</span>
|
||||
{/snippet}
|
||||
</Avatar>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm">{page.data.session?.user.name}</span>
|
||||
<span class="text-muted-foreground text-xs">{page.data.session?.user.email}</span>
|
||||
</div>
|
||||
</Button>
|
||||
{:else}
|
||||
<Button href="/login" class="w-full">Login</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</Sidebar>
|
||||
|
|
@ -2,5 +2,6 @@ import Root from './sidebar.svelte';
|
|||
import Sidebar from './sidebar-sidebar.svelte';
|
||||
import Inset from './sidebar-inset.svelte';
|
||||
import Trigger from './sidebar-trigger.svelte';
|
||||
import { useSidebarControls } from './sidebar.svelte.js';
|
||||
|
||||
export { Root, Sidebar, Inset, Trigger };
|
||||
export { Root, Sidebar, Inset, Trigger, useSidebarControls };
|
||||
|
|
|
|||
|
|
@ -19,6 +19,12 @@ export class SidebarRootState {
|
|||
this.open = !this.open;
|
||||
}
|
||||
}
|
||||
|
||||
closeMobile() {
|
||||
if (this.isMobile.current) {
|
||||
this.openMobile = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SidebarTriggerState {
|
||||
|
|
@ -35,6 +41,16 @@ export class SidebarSidebarState {
|
|||
constructor(readonly root: SidebarRootState) {}
|
||||
}
|
||||
|
||||
export class SidebarControlState {
|
||||
constructor(readonly root: SidebarRootState) {
|
||||
this.closeMobile = this.closeMobile.bind(this);
|
||||
}
|
||||
|
||||
closeMobile() {
|
||||
this.root.closeMobile();
|
||||
}
|
||||
}
|
||||
|
||||
export const ctx = new Context<SidebarRootState>('sidebar-root-context');
|
||||
|
||||
export function useSidebar() {
|
||||
|
|
@ -48,3 +64,7 @@ export function useSidebarTrigger() {
|
|||
export function useSidebarSidebar() {
|
||||
return new SidebarSidebarState(ctx.get());
|
||||
}
|
||||
|
||||
export function useSidebarControls() {
|
||||
return new SidebarControlState(ctx.get());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
import { Button } from '$lib/components/ui/button';
|
||||
import { ImageModal } from '$lib/components/ui/image-modal';
|
||||
import { LightSwitch } from '$lib/components/ui/light-switch/index.js';
|
||||
import { callModal } from '$lib/components/ui/modal/global-modal.svelte';
|
||||
import * as Sidebar from '$lib/components/ui/sidebar';
|
||||
import Tooltip from '$lib/components/ui/tooltip.svelte';
|
||||
import { cmdOrCtrl } from '$lib/hooks/is-mac.svelte.js';
|
||||
|
|
@ -22,29 +21,25 @@
|
|||
import { isString } from '$lib/utils/is.js';
|
||||
import { supportsImages } from '$lib/utils/model-capabilities';
|
||||
import { omit, pick } from '$lib/utils/object.js';
|
||||
import { cn } from '$lib/utils/utils.js';
|
||||
import { useConvexClient } from 'convex-svelte';
|
||||
import { FileUpload, Popover } from 'melt/builders';
|
||||
import { Avatar } from 'melt/components';
|
||||
import { Debounced, ElementSize, IsMounted, ScrollState } from 'runed';
|
||||
import SendIcon from '~icons/lucide/arrow-up';
|
||||
import StopIcon from '~icons/lucide/square';
|
||||
import ChevronDownIcon from '~icons/lucide/chevron-down';
|
||||
import ImageIcon from '~icons/lucide/image';
|
||||
import LoaderCircleIcon from '~icons/lucide/loader-circle';
|
||||
import PanelLeftIcon from '~icons/lucide/panel-left';
|
||||
import PinIcon from '~icons/lucide/pin';
|
||||
import PinOffIcon from '~icons/lucide/pin-off';
|
||||
import Settings2Icon from '~icons/lucide/settings-2';
|
||||
import UploadIcon from '~icons/lucide/upload';
|
||||
import XIcon from '~icons/lucide/x';
|
||||
import { callGenerateMessage } from '../api/generate-message/call.js';
|
||||
import { callCancelGeneration } from '../api/cancel-generation/call.js';
|
||||
import ModelPicker from './model-picker.svelte';
|
||||
import AppSidebar from '$lib/components/app-sidebar.svelte';
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
let { data, children } = $props();
|
||||
let { children } = $props();
|
||||
|
||||
let form = $state<HTMLFormElement>();
|
||||
let textarea = $state<HTMLTextAreaElement>();
|
||||
|
|
@ -118,10 +113,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
const conversationsQuery = useCachedQuery(api.conversations.get, {
|
||||
session_token: session.current?.session.token ?? '',
|
||||
});
|
||||
|
||||
const openRouterKeyQuery = useCachedQuery(api.user_keys.get, {
|
||||
provider: Provider.OpenRouter,
|
||||
session_token: session.current?.session.token ?? '',
|
||||
|
|
@ -133,92 +124,6 @@
|
|||
|
||||
const autosize = new TextareaAutosize();
|
||||
|
||||
function groupConversationsByTime(conversations: Doc<'conversations'>[]) {
|
||||
const now = Date.now();
|
||||
const oneDay = 24 * 60 * 60 * 1000;
|
||||
const sevenDays = 7 * oneDay;
|
||||
const thirtyDays = 30 * oneDay;
|
||||
|
||||
const groups = {
|
||||
pinned: [] as Doc<'conversations'>[],
|
||||
today: [] as Doc<'conversations'>[],
|
||||
yesterday: [] as Doc<'conversations'>[],
|
||||
lastWeek: [] as Doc<'conversations'>[],
|
||||
lastMonth: [] as Doc<'conversations'>[],
|
||||
older: [] as Doc<'conversations'>[],
|
||||
};
|
||||
|
||||
conversations.forEach((conversation) => {
|
||||
// Pinned conversations go to pinned group regardless of time
|
||||
if (conversation.pinned) {
|
||||
groups.pinned.push(conversation);
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedAt = conversation.updated_at ?? 0;
|
||||
const timeDiff = now - updatedAt;
|
||||
|
||||
if (timeDiff < oneDay) {
|
||||
groups.today.push(conversation);
|
||||
} else if (timeDiff < 2 * oneDay) {
|
||||
groups.yesterday.push(conversation);
|
||||
} else if (timeDiff < sevenDays) {
|
||||
groups.lastWeek.push(conversation);
|
||||
} else if (timeDiff < thirtyDays) {
|
||||
groups.lastMonth.push(conversation);
|
||||
} else {
|
||||
groups.older.push(conversation);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort pinned conversations by updated_at (most recent first)
|
||||
groups.pinned.sort((a, b) => {
|
||||
const aTime = a.updated_at ?? 0;
|
||||
const bTime = b.updated_at ?? 0;
|
||||
return bTime - aTime;
|
||||
});
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
const groupedConversations = $derived(groupConversationsByTime(conversationsQuery.data ?? []));
|
||||
|
||||
async function togglePin(conversationId: string) {
|
||||
if (!session.current?.session.token) return;
|
||||
|
||||
await client.mutation(api.conversations.togglePin, {
|
||||
conversation_id: conversationId as Id<'conversations'>,
|
||||
session_token: session.current.session.token,
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteConversation(conversationId: string) {
|
||||
const res = await callModal({
|
||||
title: 'Delete conversation',
|
||||
description: 'Are you sure you want to delete this conversation?',
|
||||
actions: { cancel: 'outline', delete: 'destructive' },
|
||||
});
|
||||
|
||||
if (res !== 'delete') return;
|
||||
|
||||
if (!session.current?.session.token) return;
|
||||
|
||||
await client.mutation(api.conversations.remove, {
|
||||
conversation_id: conversationId as Id<'conversations'>,
|
||||
session_token: session.current.session.token,
|
||||
});
|
||||
goto(`/chat`);
|
||||
}
|
||||
|
||||
const templateConversations = $derived([
|
||||
{ key: 'pinned', label: 'Pinned', conversations: groupedConversations.pinned, icon: PinIcon },
|
||||
{ key: 'today', label: 'Today', conversations: groupedConversations.today },
|
||||
{ key: 'yesterday', label: 'Yesterday', conversations: groupedConversations.yesterday },
|
||||
{ key: 'lastWeek', label: 'Last 7 days', conversations: groupedConversations.lastWeek },
|
||||
{ key: 'lastMonth', label: 'Last 30 days', conversations: groupedConversations.lastMonth },
|
||||
{ key: 'older', label: 'Older', conversations: groupedConversations.older },
|
||||
]);
|
||||
|
||||
let message = $state('');
|
||||
let selectedImages = $state<{ url: string; storage_id: string; fileName?: string }[]>([]);
|
||||
let isUploading = $state(false);
|
||||
|
|
@ -447,142 +352,7 @@
|
|||
class="h-screen overflow-clip"
|
||||
{...currentModelSupportsImages ? omit(fileUpload.dropzone, ['onclick']) : {}}
|
||||
>
|
||||
<Sidebar.Sidebar class="flex flex-col overflow-clip p-2">
|
||||
<div class="flex place-items-center justify-center py-2">
|
||||
<span class="text-center font-serif text-lg">Thom.chat</span>
|
||||
</div>
|
||||
<div class="mt-1 flex w-full px-2">
|
||||
<Tooltip>
|
||||
{#snippet trigger(tooltip)}
|
||||
<a
|
||||
href="/chat"
|
||||
class="border-reflect button-reflect bg-primary/20 hover:bg-primary/50 font-fake-proxima w-full rounded-lg px-4 py-2 text-center text-sm tracking-[-0.005em] duration-200"
|
||||
{...tooltip.trigger}
|
||||
style="font-variation-settings: 'wght' 750"
|
||||
>
|
||||
New Chat
|
||||
</a>
|
||||
{/snippet}
|
||||
{cmdOrCtrl} + Shift + O
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="relative flex min-h-0 flex-1 shrink-0 flex-col overflow-clip">
|
||||
<div
|
||||
class="from-sidebar pointer-events-none absolute top-0 right-0 left-0 z-10 h-4 bg-gradient-to-b to-transparent"
|
||||
></div>
|
||||
<div class="flex flex-1 flex-col overflow-y-auto py-2">
|
||||
{#each templateConversations as group, index (group.key)}
|
||||
{@const IconComponent = group.icon}
|
||||
{#if group.conversations.length > 0}
|
||||
<div class="px-2 py-1" class:mt-2={index > 0}>
|
||||
<h3 class="text-heading text-xs font-medium">
|
||||
{#if IconComponent}
|
||||
<IconComponent class="inline size-3" />
|
||||
{/if}
|
||||
{group.label}
|
||||
</h3>
|
||||
</div>
|
||||
{#each group.conversations as conversation (conversation._id)}
|
||||
{@const isActive = page.params.id === conversation._id}
|
||||
<a
|
||||
href={`/chat/${conversation._id}`}
|
||||
class="group w-full py-0.5 pr-2.5 text-left text-sm"
|
||||
>
|
||||
<div
|
||||
class={cn(
|
||||
'relative flex w-full items-center justify-between overflow-clip rounded-lg',
|
||||
{ 'bg-sidebar-accent': isActive, 'group-hover:bg-sidebar-accent': !isActive }
|
||||
)}
|
||||
>
|
||||
<p class="truncate rounded-lg py-2 pr-4 pl-3 whitespace-nowrap">
|
||||
<span>{conversation.title}</span>
|
||||
</p>
|
||||
<div class="pr-2">
|
||||
{#if conversation.generating}
|
||||
<div
|
||||
class="flex animate-[spin_0.75s_linear_infinite] place-items-center justify-center"
|
||||
>
|
||||
<LoaderCircleIcon class="size-4" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div
|
||||
class={[
|
||||
'pointer-events-none absolute inset-y-0.5 right-0 flex translate-x-full items-center gap-2 rounded-r-lg pr-2 pl-6 transition group-hover:pointer-events-auto group-hover:translate-0',
|
||||
'to-sidebar-accent via-sidebar-accent bg-gradient-to-r from-transparent from-10% via-21% ',
|
||||
]}
|
||||
>
|
||||
<Tooltip>
|
||||
{#snippet trigger(tooltip)}
|
||||
<button
|
||||
{...tooltip.trigger}
|
||||
class="hover:bg-muted rounded-md p-1"
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
togglePin(conversation._id);
|
||||
}}
|
||||
>
|
||||
{#if conversation.pinned}
|
||||
<PinOffIcon class="size-4" />
|
||||
{:else}
|
||||
<PinIcon class="size-4" />
|
||||
{/if}
|
||||
</button>
|
||||
{/snippet}
|
||||
{conversation.pinned ? 'Unpin thread' : 'Pin thread'}
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
{#snippet trigger(tooltip)}
|
||||
<button
|
||||
{...tooltip.trigger}
|
||||
class="hover:bg-muted rounded-md p-1"
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
deleteConversation(conversation._id);
|
||||
}}
|
||||
>
|
||||
<XIcon class="size-4" />
|
||||
</button>
|
||||
{/snippet}
|
||||
Delete thread
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div
|
||||
class="from-sidebar pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-4 bg-gradient-to-t to-transparent"
|
||||
></div>
|
||||
</div>
|
||||
<div class="py-2">
|
||||
{#if data.session !== null}
|
||||
<Button href="/account" variant="ghost" class="h-auto w-full justify-start">
|
||||
<Avatar src={data.session?.user.image ?? undefined}>
|
||||
{#snippet children(avatar)}
|
||||
<img {...avatar.image} alt="Your avatar" class="size-10 rounded-full" />
|
||||
<span {...avatar.fallback} class="size-10 rounded-full">
|
||||
{data.session?.user.name
|
||||
.split(' ')
|
||||
.map((name) => name[0]?.toUpperCase())
|
||||
.join('')}
|
||||
</span>
|
||||
{/snippet}
|
||||
</Avatar>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm">{data.session?.user.name}</span>
|
||||
<span class="text-muted-foreground text-xs">{data.session?.user.email}</span>
|
||||
</div>
|
||||
</Button>
|
||||
{:else}
|
||||
<Button href="/login" class="w-full">Login</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</Sidebar.Sidebar>
|
||||
<AppSidebar />
|
||||
|
||||
<Sidebar.Inset class="w-full overflow-clip px-2">
|
||||
<Tooltip>
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@
|
|||
<Button
|
||||
onclick={() => (prompt.current = suggestion)}
|
||||
variant="ghost"
|
||||
class="w-full px-2 cursor-pointer justify-start py-2 text-start"
|
||||
class="w-full cursor-pointer justify-start px-2 py-2 text-start"
|
||||
>
|
||||
{suggestion}
|
||||
</Button>
|
||||
|
|
@ -117,7 +117,7 @@
|
|||
<Button
|
||||
onclick={() => (prompt.current = suggestion)}
|
||||
variant="ghost"
|
||||
class="w-full px-2 cursor-pointer justify-start py-2 text-start group-last:line-through"
|
||||
class="w-full cursor-pointer justify-start px-2 py-2 text-start group-last:line-through"
|
||||
>
|
||||
{suggestion}
|
||||
</Button>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue