fix: chat session reliability, cost, and hygiene (empty-response guard, tool truncation, delete, titles)
- Empty/refusal responses retried once, then surfaced as errors instead of silent blanks
- Chinese refusal boilerplate detected via denylist + non-ASCII heuristic
- Bulk-tool preference added to SOUL.md (list_lxcs over per-entity get_lxc_state)
- Tool results truncated to 4KB on persist; get_state_snapshot filters null-state entities
- Session delete (DELETE /sessions/{id} + confirm-on-second-click UI)
- Session titles auto-generated from assistant answer instead of raw user message
This commit is contained in:
@@ -1,29 +1,59 @@
|
||||
<script lang="ts">
|
||||
import { sessions, loadSessions, loadSessionMessages, messages, currentSession } from '$lib/stores/chat'
|
||||
import { sessions, loadSessions, loadSessionMessages, deleteSession, currentSession } from '$lib/stores/chat'
|
||||
import { onMount } from 'svelte'
|
||||
import Trash2Icon from '@lucide/svelte/icons/trash2'
|
||||
|
||||
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)}
|
||||
<button
|
||||
class="session-card"
|
||||
<div
|
||||
class="session-card group"
|
||||
class:active={$currentSession === session.id}
|
||||
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-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}
|
||||
@@ -54,15 +84,12 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
text-align: left;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
|
||||
@@ -75,6 +102,58 @@
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user