Files
oikos/web/src/pages/Sessions.svelte
dtoro e92a6ff7a5
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
fix: trash-2 icon name (lucide uses trash-2, not trash2)
2026-07-09 10:21:22 +02:00

173 lines
3.7 KiB
Svelte

<script lang="ts">
import { sessions, loadSessions, loadSessionMessages, deleteSession, currentSession } from '$lib/stores/chat'
import { onMount } from 'svelte'
import Trash2Icon from '@lucide/svelte/icons/trash-2'
onMount(() => {
loadSessions()
})
let confirmDelete = $state<string | null>(null)
function handleDelete(e: MouseEvent, id: string) {
e.stopPropagation()
if (confirmDelete === id) {
deleteSession(id)
confirmDelete = null
} else {
confirmDelete = id
setTimeout(() => { if (confirmDelete === id) confirmDelete = null }, 3000)
}
}
</script>
<div class="sessions-page">
<h2>Sessions</h2>
<div class="session-list">
{#each $sessions as session (session.id)}
<div
class="session-card group"
class:active={$currentSession === session.id}
>
<button
class="session-content"
onclick={() => {
loadSessionMessages(session.id)
location.hash = '#/chat'
}}
>
<div class="session-title">{session.title || 'Untitled'}</div>
<div class="session-meta">
{new Date(session.last_active_at).toLocaleString()}
</div>
</button>
<button
class="session-delete"
onclick={(e) => handleDelete(e, session.id)}
title={confirmDelete === session.id ? 'Click again to confirm delete' : 'Delete session'}
aria-label="Delete session"
>
{#if confirmDelete === session.id}
<span class="confirm-text">Delete?</span>
{:else}
<Trash2Icon class="icon" />
{/if}
</button>
</div>
{:else}
<div class="empty">No sessions yet. Start chatting with Nomos.</div>
{/each}
</div>
</div>
<style>
.sessions-page {
max-width: 720px;
margin: 0 auto;
padding: 2rem 1rem;
}
h2 {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 1rem;
color: var(--text);
}
.session-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.session-card {
display: flex;
justify-content: space-between;
align-items: center;
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-family: inherit;
font-size: 0.875rem;
transition: border-color 0.15s;
}
.session-card:hover {
border-color: var(--accent-blue);
}
.session-card.active {
border-color: var(--accent-blue);
background: var(--bg-hover);
}
.session-content {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 1rem;
background: none;
border: none;
cursor: pointer;
color: inherit;
font-family: inherit;
font-size: inherit;
text-align: left;
}
.session-delete {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
margin-right: 4px;
padding: 0;
background: none;
border: none;
border-radius: 6px;
cursor: pointer;
color: var(--text-muted);
opacity: 0;
transition: opacity 0.15s, background 0.15s;
}
.group:hover .session-delete {
opacity: 1;
}
.session-delete:hover {
background: var(--destructive-bg-subtle, rgba(239, 68, 68, 0.1));
color: var(--destructive, #ef4444);
}
.confirm-text {
font-size: 0.65rem;
font-weight: 700;
color: var(--destructive, #ef4444);
}
.icon {
width: 16px;
height: 16px;
}
.session-title {
font-weight: 500;
}
.session-meta {
font-size: 0.75rem;
color: var(--text-muted);
}
.empty {
color: var(--text-muted);
font-size: 0.875rem;
padding: 2rem 0;
text-align: center;
}
</style>