Files
hermes-chat-app/src/lib/components/chat/Sidebar.svelte

103 lines
3.8 KiB
Svelte

<script lang="ts">
import { sessions, activeSessionId, createSession } from '$lib/stores/chat';
import { isStreaming } from '$lib/stores/chat';
import { get } from 'svelte/store';
import { Separator } from '$lib/components/ui/separator/index.js';
import { getHermesStatus } from '$lib/hermes';
import { onMount } from 'svelte';
let { onClose }: { onClose?: () => void } = $props();
let hermesAvailable = $state(false);
let hermesModel = $state('');
let sessionList = $state<any[]>([]);
let activeId = $state<string | null>(null);
sessions.subscribe((v) => (sessionList = v));
activeSessionId.subscribe((v) => (activeId = v));
onMount(async () => {
try {
const status = await getHermesStatus();
hermesAvailable = status.available;
hermesModel = status.model || status.provider || 'connected';
} catch {
hermesAvailable = false;
}
});
function formatDate(ts: number) {
const d = new Date(ts);
const now = new Date();
const diff = now.getTime() - d.getTime();
if (diff < 86400000) return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
if (diff < 604800000) return d.toLocaleDateString([], { weekday: 'short' });
return d.toLocaleDateString([], { month: 'short', day: 'numeric' });
}
</script>
<aside class="flex h-full w-72 flex-col border-r bg-muted/30">
<!-- New chat button -->
<div class="p-3">
<button
class="flex w-full items-center gap-2 rounded-lg border border-dashed border-border px-3 py-2.5 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors disabled:opacity-50"
onclick={() => { createSession(); onClose?.(); }}
disabled={get(isStreaming)}
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
New Chat
</button>
</div>
<Separator />
<!-- Sessions list -->
<div class="flex-1 overflow-y-auto p-2 space-y-1">
{#if sessionList.length === 0}
<div class="flex flex-col items-center justify-center py-12 text-center px-4">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="text-muted-foreground/40 mb-3">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
</svg>
<p class="text-sm text-muted-foreground">No conversations yet</p>
<p class="text-xs text-muted-foreground/60 mt-1">Start a new chat above</p>
</div>
{/if}
{#each sessionList as session (session.id)}
<button
class="w-full text-left rounded-lg px-3 py-2.5 transition-colors group
{session.id === activeId
? 'bg-accent text-accent-foreground'
: 'hover:bg-accent/50 text-muted-foreground hover:text-foreground'}"
onclick={() => {
activeSessionId.set(session.id);
onClose?.();
}}
>
<div class="flex items-start justify-between gap-2">
<span class="text-sm font-medium truncate flex-1">{session.title}</span>
<span class="text-[10px] text-muted-foreground/60 whitespace-nowrap mt-0.5">
{formatDate(session.timestamp)}
</span>
</div>
{#if session.preview}
<p class="text-xs text-muted-foreground/70 mt-1 truncate">{session.preview}</p>
{/if}
</button>
{/each}
</div>
<!-- Footer status -->
<div class="border-t p-3">
<div class="flex items-center gap-2">
<div class="h-2 w-2 rounded-full {hermesAvailable ? 'bg-green-500' : 'bg-red-500'}" />
<span class="text-xs text-muted-foreground truncate">
{hermesAvailable ? (hermesModel || 'Hermes Agent') : 'Hermes disconnected'}
</span>
</div>
</div>
</aside>