Add delete controls to resume sessions

This commit is contained in:
Shantur Rathore
2025-12-06 22:16:08 +00:00
parent e345dc1262
commit d68cb6b1b8

View File

@@ -1,8 +1,9 @@
import { Component, createSignal, Show, For, createEffect, onMount, onCleanup, createMemo } from "solid-js" import { Component, createSignal, Show, For, createEffect, onMount, onCleanup, createMemo } from "solid-js"
import { Trash2 } from "lucide-solid"
import type { Instance } from "../types/instance" import type { Instance } from "../types/instance"
import { getParentSessions, createSession, setActiveParentSession } from "../stores/sessions" import { getParentSessions, createSession, setActiveParentSession, deleteSession, loading } from "../stores/sessions"
import InstanceInfo from "./instance-info" import InstanceInfo from "./instance-info"
import KeyboardHint from "./keyboard-hint"
import Kbd from "./kbd" import Kbd from "./kbd"
import { keyboardRegistry, type KeyboardShortcut } from "../lib/keyboard-registry" import { keyboardRegistry, type KeyboardShortcut } from "../lib/keyboard-registry"
import { isMac } from "../lib/keyboard-utils" import { isMac } from "../lib/keyboard-utils"
@@ -22,6 +23,10 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
const [showInstanceInfoOverlay, setShowInstanceInfoOverlay] = createSignal(false) const [showInstanceInfoOverlay, setShowInstanceInfoOverlay] = createSignal(false)
const parentSessions = () => getParentSessions(props.instance.id) const parentSessions = () => getParentSessions(props.instance.id)
const isSessionDeleting = (sessionId: string) => {
const deleting = loading().deletingSession.get(props.instance.id)
return deleting ? deleting.has(sessionId) : false
}
const newSessionShortcut = createMemo<KeyboardShortcut>(() => { const newSessionShortcut = createMemo<KeyboardShortcut>(() => {
const registered = keyboardRegistry.get("session-new") const registered = keyboardRegistry.get("session-new")
if (registered) return registered if (registered) return registered
@@ -119,21 +124,48 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
scrollToIndex(newIndex) scrollToIndex(newIndex)
} else if (e.key === "Enter") { } else if (e.key === "Enter") {
e.preventDefault() e.preventDefault()
handleEnterKey() void handleEnterKey()
} else if (e.key === "Delete" || e.key === "Backspace") {
e.preventDefault()
void handleDeleteKey()
} }
} }
async function handleEnterKey() { async function handleEnterKey() {
const sessions = parentSessions() const sessions = parentSessions()
const index = selectedIndex() const index = selectedIndex()
if (index < sessions.length) { if (index < sessions.length) {
await handleSessionSelect(sessions[index].id) await handleSessionSelect(sessions[index].id)
} }
} }
onMount(() => { async function handleDeleteKey() {
const sessions = parentSessions()
const index = selectedIndex()
if (index >= sessions.length) {
return
}
await handleSessionDelete(sessions[index].id)
const updatedSessions = parentSessions()
if (updatedSessions.length === 0) {
setFocusMode("new-session")
setSelectedIndex(0)
return
}
const nextIndex = Math.min(index, updatedSessions.length - 1)
setSelectedIndex(nextIndex)
setFocusMode("sessions")
scrollToIndex(nextIndex)
}
onMount(() => {
window.addEventListener("keydown", handleKeyDown) window.addEventListener("keydown", handleKeyDown)
onCleanup(() => { onCleanup(() => {
window.removeEventListener("keydown", handleKeyDown) window.removeEventListener("keydown", handleKeyDown)
}) })
@@ -184,10 +216,21 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
setActiveParentSession(props.instance.id, sessionId) setActiveParentSession(props.instance.id, sessionId)
} }
async function handleSessionDelete(sessionId: string) {
if (isSessionDeleting(sessionId)) return
try {
await deleteSession(props.instance.id, sessionId)
} catch (error) {
log.error("Failed to delete session:", error)
}
}
async function handleNewSession() { async function handleNewSession() {
if (isCreating()) return if (isCreating()) return
setIsCreating(true) setIsCreating(true)
try { try {
const session = await createSession(props.instance.id) const session = await createSession(props.instance.id)
setActiveParentSession(props.instance.id, session.id) setActiveParentSession(props.instance.id, session.id)
@@ -248,48 +291,82 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
</div> </div>
<div class="panel-list panel-list--fill flex-1 min-h-0 overflow-auto"> <div class="panel-list panel-list--fill flex-1 min-h-0 overflow-auto">
<For each={parentSessions()}> <For each={parentSessions()}>
{(session, index) => ( {(session, index) => {
<div const isFocused = () => focusMode() === "sessions" && selectedIndex() === index()
class="panel-list-item" return (
classList={{ <div
"panel-list-item-highlight": focusMode() === "sessions" && selectedIndex() === index(), class="panel-list-item"
}} classList={{
> "panel-list-item-highlight": isFocused(),
<button
data-session-index={index()}
class="panel-list-item-content group w-full"
onClick={() => handleSessionSelect(session.id)}
onMouseEnter={() => {
setFocusMode("sessions")
setSelectedIndex(index())
}} }}
> >
<div class="flex items-center justify-between gap-3 w-full"> <div class="flex items-center gap-2 w-full px-1">
<div class="flex-1 min-w-0"> <button
<div class="flex items-center gap-2"> type="button"
<span data-session-index={index()}
class="text-sm font-medium text-primary truncate transition-colors" class="panel-list-item-content group flex-1"
classList={{ onClick={() => handleSessionSelect(session.id)}
"text-accent": onMouseEnter={() => {
focusMode() === "sessions" && selectedIndex() === index(), setFocusMode("sessions")
setSelectedIndex(index())
}}
>
<div class="flex items-center justify-between gap-3 w-full">
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2">
<span
class="text-sm font-medium text-primary truncate transition-colors"
classList={{
"text-accent": isFocused(),
}}
>
{session.title || "Untitled Session"}
</span>
</div>
<div class="flex items-center gap-3 text-xs text-muted mt-0.5">
<span>{session.agent}</span>
<span></span>
<span>{formatRelativeTime(session.time.updated)}</span>
</div>
</div>
</div>
</button>
<Show when={isFocused()}>
<div class="flex items-center gap-2 flex-shrink-0">
<kbd class="kbd flex-shrink-0"></kbd>
<button
type="button"
class="p-1.5 rounded transition-colors text-muted hover:text-red-500 dark:hover:text-red-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
title="Delete session"
disabled={isSessionDeleting(session.id)}
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
void handleSessionDelete(session.id)
}} }}
> >
{session.title || "Untitled Session"} <Show
</span> when={!isSessionDeleting(session.id)}
fallback={
<svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
}
>
<Trash2 class="w-4 h-4" />
</Show>
</button>
</div> </div>
<div class="flex items-center gap-3 text-xs text-muted mt-0.5">
<span>{session.agent}</span>
<span></span>
<span>{formatRelativeTime(session.time.updated)}</span>
</div>
</div>
<Show when={focusMode() === "sessions" && selectedIndex() === index()}>
<kbd class="kbd flex-shrink-0"></kbd>
</Show> </Show>
</div> </div>
</button> </div>
</div> )
)} }}
</For> </For>
</div> </div>
</div> </div>
@@ -363,6 +440,7 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
</Show> </Show>
<div class="panel-footer hidden sm:block"> <div class="panel-footer hidden sm:block">
<div class="panel-footer-hints"> <div class="panel-footer-hints">
<div class="flex items-center gap-1.5"> <div class="flex items-center gap-1.5">
<kbd class="kbd"></kbd> <kbd class="kbd"></kbd>
@@ -383,12 +461,16 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
<kbd class="kbd">Enter</kbd> <kbd class="kbd">Enter</kbd>
<span>Resume</span> <span>Resume</span>
</div> </div>
<KeyboardHint shortcuts={[newSessionShortcut()]} separator="" /> <div class="flex items-center gap-1.5">
<kbd class="kbd">Del</kbd>
<span>Delete</span>
</div>
</div> </div>
</div> </div>
</div> </div>
) )
} }
export default InstanceWelcomeView
export default InstanceWelcomeView