feat: add session history sidebar with delete and navigation
- SessionSidebar component listing past sessions sorted by recency - Session title preview and relative date display - Click to switch sessions, delete with confirmation - Added deleteSession method to session store - Integrated sidebar into chat page layout Closes #12 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
81
src/lib/components/SessionSidebar.svelte
Normal file
81
src/lib/components/SessionSidebar.svelte
Normal file
@@ -0,0 +1,81 @@
|
||||
<script lang="ts">
|
||||
import { sessionStore } from '$lib/stores/sessions.svelte';
|
||||
|
||||
let {
|
||||
onSelectSession,
|
||||
onNewChat
|
||||
}: { onSelectSession: (id: string) => void; onNewChat: () => void } = $props();
|
||||
|
||||
let confirmDeleteId: string | null = $state(null);
|
||||
|
||||
const sessions = $derived(sessionStore.getAllSessions());
|
||||
const activeId = $derived(sessionStore.activeSessionId);
|
||||
|
||||
function handleDelete(e: MouseEvent, id: string) {
|
||||
e.stopPropagation();
|
||||
if (confirmDeleteId === id) {
|
||||
sessionStore.deleteSession(id);
|
||||
confirmDeleteId = null;
|
||||
if (sessionStore.activeSessionId) {
|
||||
onSelectSession(sessionStore.activeSessionId);
|
||||
}
|
||||
} else {
|
||||
confirmDeleteId = id;
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(date: Date): string {
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - date.getTime();
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
if (days === 0) return 'Today';
|
||||
if (days === 1) return 'Yesterday';
|
||||
if (days < 7) return `${days} days ago`;
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
</script>
|
||||
|
||||
<aside class="flex h-full w-64 flex-col border-r border-gray-200 bg-gray-50">
|
||||
<div class="border-b border-gray-200 p-3">
|
||||
<button
|
||||
type="button"
|
||||
onclick={onNewChat}
|
||||
class="w-full rounded-lg bg-blue-600 px-3 py-2 text-sm font-medium text-white hover:bg-blue-700"
|
||||
>
|
||||
+ New Chat
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
{#if sessions.length === 0}
|
||||
<p class="p-4 text-center text-sm text-gray-400">No sessions yet</p>
|
||||
{:else}
|
||||
{#each sessions as session (session.id)}
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => onSelectSession(session.id)}
|
||||
onkeydown={(e) => { if (e.key === 'Enter') onSelectSession(session.id); }}
|
||||
class="group flex w-full cursor-pointer items-start gap-2 border-b border-gray-100 px-3 py-3 text-left hover:bg-gray-100
|
||||
{activeId === session.id ? 'bg-blue-50 border-l-2 border-l-blue-500' : ''}"
|
||||
>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium text-gray-900">{session.title}</p>
|
||||
<p class="mt-0.5 text-xs text-gray-500">{formatDate(session.createdAt)}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onclick={(e) => handleDelete(e, session.id)}
|
||||
class="shrink-0 rounded p-1 text-xs opacity-0 group-hover:opacity-100
|
||||
{confirmDeleteId === session.id
|
||||
? 'bg-red-100 text-red-600'
|
||||
: 'text-gray-400 hover:bg-gray-200 hover:text-gray-600'}"
|
||||
title={confirmDeleteId === session.id ? 'Click again to confirm' : 'Delete session'}
|
||||
>
|
||||
{confirmDeleteId === session.id ? '✓' : '✕'}
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</aside>
|
||||
Reference in New Issue
Block a user