From 22a4d2a7f323a9a0afb7b828d6d5001534296d4d Mon Sep 17 00:00:00 2001 From: Aidan Bleser Date: Wed, 18 Jun 2025 06:24:36 -0500 Subject: [PATCH] move and improve sidebar --- src/lib/components/app-sidebar.svelte | 253 ++++++++++++++++++ src/lib/components/ui/sidebar/index.ts | 3 +- .../components/ui/sidebar/sidebar.svelte.ts | 20 ++ src/routes/chat/+layout.svelte | 236 +--------------- src/routes/chat/+page.svelte | 4 +- 5 files changed, 280 insertions(+), 236 deletions(-) create mode 100644 src/lib/components/app-sidebar.svelte diff --git a/src/lib/components/app-sidebar.svelte b/src/lib/components/app-sidebar.svelte new file mode 100644 index 0000000..59eb886 --- /dev/null +++ b/src/lib/components/app-sidebar.svelte @@ -0,0 +1,253 @@ + + + +
+ Thom.chat +
+
+ + {#snippet trigger(tooltip)} + + New Chat + + {/snippet} + {cmdOrCtrl} + Shift + O + +
+
+
+
+ {#each templateConversations as group, index (group.key)} + {@const IconComponent = group.icon} + {#if group.conversations.length > 0} +
0}> +

+ {#if IconComponent} + + {/if} + {group.label} +

+
+ {#each group.conversations as conversation (conversation._id)} + {@const isActive = page.params.id === conversation._id} + +
+

+ {conversation.title} +

+
+ {#if conversation.generating} +
+ +
+ {/if} +
+
+ + {#snippet trigger(tooltip)} + + {/snippet} + {conversation.pinned ? 'Unpin thread' : 'Pin thread'} + + + {#snippet trigger(tooltip)} + + {/snippet} + Delete thread + +
+
+
+ {/each} + {/if} + {/each} +
+
+
+
+ {#if page.data.session !== null} + + {:else} + + {/if} +
+
diff --git a/src/lib/components/ui/sidebar/index.ts b/src/lib/components/ui/sidebar/index.ts index 4166cc4..ae66ade 100644 --- a/src/lib/components/ui/sidebar/index.ts +++ b/src/lib/components/ui/sidebar/index.ts @@ -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 }; diff --git a/src/lib/components/ui/sidebar/sidebar.svelte.ts b/src/lib/components/ui/sidebar/sidebar.svelte.ts index d4ac4cb..7c85f44 100644 --- a/src/lib/components/ui/sidebar/sidebar.svelte.ts +++ b/src/lib/components/ui/sidebar/sidebar.svelte.ts @@ -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('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()); +} diff --git a/src/routes/chat/+layout.svelte b/src/routes/chat/+layout.svelte index 057dd62..2b3e3f5 100644 --- a/src/routes/chat/+layout.svelte +++ b/src/routes/chat/+layout.svelte @@ -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(); let textarea = $state(); @@ -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']) : {}} > - -
- Thom.chat -
-
- - {#snippet trigger(tooltip)} - - New Chat - - {/snippet} - {cmdOrCtrl} + Shift + O - -
-
-
-
- {#each templateConversations as group, index (group.key)} - {@const IconComponent = group.icon} - {#if group.conversations.length > 0} -
0}> -

- {#if IconComponent} - - {/if} - {group.label} -

-
- {#each group.conversations as conversation (conversation._id)} - {@const isActive = page.params.id === conversation._id} - -
-

- {conversation.title} -

-
- {#if conversation.generating} -
- -
- {/if} -
-
- - {#snippet trigger(tooltip)} - - {/snippet} - {conversation.pinned ? 'Unpin thread' : 'Pin thread'} - - - {#snippet trigger(tooltip)} - - {/snippet} - Delete thread - -
-
-
- {/each} - {/if} - {/each} -
-
-
-
- {#if data.session !== null} - - {:else} - - {/if} -
-
+ diff --git a/src/routes/chat/+page.svelte b/src/routes/chat/+page.svelte index 6765121..b24525b 100644 --- a/src/routes/chat/+page.svelte +++ b/src/routes/chat/+page.svelte @@ -105,7 +105,7 @@ @@ -117,7 +117,7 @@