managed sessions only. allow for rename/delete
This commit is contained in:
@@ -1,13 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { onMount, onDestroy, tick } from 'svelte';
|
||||
import { activeSession } from '$lib/stores/sessions';
|
||||
import MessageList from '$lib/components/MessageList.svelte';
|
||||
import InputBar from '$lib/components/InputBar.svelte';
|
||||
import PermissionRequest from '$lib/components/PermissionRequest.svelte';
|
||||
|
||||
$: sessionId = $page.params.id;
|
||||
|
||||
let inputBar: InputBar;
|
||||
let steerMode = false;
|
||||
let isEditingTitle = false;
|
||||
let editedTitle = '';
|
||||
let titleInput: HTMLInputElement;
|
||||
|
||||
onMount(() => {
|
||||
if (sessionId) {
|
||||
activeSession.load(sessionId);
|
||||
@@ -19,13 +26,64 @@
|
||||
});
|
||||
|
||||
function handleSend(event: CustomEvent<string>) {
|
||||
activeSession.sendMessage(event.detail);
|
||||
if (steerMode && $activeSession.pendingPermission) {
|
||||
// Send as steer response
|
||||
activeSession.respondToPermission('steer', event.detail);
|
||||
steerMode = false;
|
||||
} else {
|
||||
activeSession.sendMessage(event.detail);
|
||||
}
|
||||
}
|
||||
|
||||
function handlePermissionAccept() {
|
||||
activeSession.respondToPermission('accept');
|
||||
}
|
||||
|
||||
function handlePermissionDeny() {
|
||||
activeSession.respondToPermission('deny');
|
||||
}
|
||||
|
||||
function handlePermissionSteer() {
|
||||
steerMode = true;
|
||||
// Focus the input bar
|
||||
inputBar?.focus();
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
goto('/');
|
||||
}
|
||||
|
||||
async function startEditingTitle() {
|
||||
if (!session) return;
|
||||
editedTitle = session.title || '';
|
||||
isEditingTitle = true;
|
||||
await tick();
|
||||
titleInput?.focus();
|
||||
titleInput?.select();
|
||||
}
|
||||
|
||||
async function saveTitle() {
|
||||
if (!session || !isEditingTitle) return;
|
||||
const newTitle = editedTitle.trim();
|
||||
isEditingTitle = false;
|
||||
if (newTitle !== (session.title || '')) {
|
||||
await activeSession.rename(newTitle);
|
||||
}
|
||||
}
|
||||
|
||||
function cancelEditTitle() {
|
||||
isEditingTitle = false;
|
||||
editedTitle = '';
|
||||
}
|
||||
|
||||
function handleTitleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
saveTitle();
|
||||
} else if (event.key === 'Escape') {
|
||||
cancelEditTitle();
|
||||
}
|
||||
}
|
||||
|
||||
$: session = $activeSession.session;
|
||||
$: externalId = session?.['external-id'] || session?.externalId || '';
|
||||
$: workingDir = session?.['working-dir'] || session?.workingDir || '';
|
||||
@@ -66,9 +124,24 @@
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full {statusColors[session.status] || statusColors.idle}"></span>
|
||||
<h1 class="font-semibold truncate">
|
||||
{session.title || `Session ${shortId}`}
|
||||
</h1>
|
||||
{#if isEditingTitle}
|
||||
<input
|
||||
bind:this={titleInput}
|
||||
bind:value={editedTitle}
|
||||
on:blur={saveTitle}
|
||||
on:keydown={handleTitleKeydown}
|
||||
class="font-semibold bg-zinc-800 border border-zinc-600 rounded px-2 py-0.5 text-zinc-100 focus:outline-none focus:border-spice-500 w-full max-w-[200px]"
|
||||
placeholder="Session name"
|
||||
/>
|
||||
{:else}
|
||||
<button
|
||||
on:click={startEditingTitle}
|
||||
class="font-semibold truncate text-left hover:text-spice-400 transition-colors"
|
||||
title="Click to rename"
|
||||
>
|
||||
{session.title || `Session ${shortId}`}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if projectName}
|
||||
<p class="text-xs text-zinc-500 truncate">{projectName}</p>
|
||||
@@ -107,11 +180,33 @@
|
||||
</svg>
|
||||
</div>
|
||||
{:else}
|
||||
<MessageList messages={$activeSession.messages} streamingContent={$activeSession.streamingContent} />
|
||||
{#if workingDir}
|
||||
<div class="flex-shrink-0 px-4 py-2 bg-zinc-900/50 border-b border-zinc-800 flex items-center gap-2 text-xs text-zinc-500">
|
||||
<svg class="h-3.5 w-3.5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
||||
</svg>
|
||||
<span class="truncate font-mono">{workingDir}</span>
|
||||
</div>
|
||||
{/if}
|
||||
<MessageList messages={$activeSession.messages} streamingContent={$activeSession.streamingContent} isThinking={$activeSession.isThinking} />
|
||||
|
||||
{#if $activeSession.pendingPermission}
|
||||
<PermissionRequest
|
||||
permission={$activeSession.pendingPermission}
|
||||
on:accept={handlePermissionAccept}
|
||||
on:deny={handlePermissionDeny}
|
||||
on:steer={handlePermissionSteer}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<InputBar
|
||||
bind:this={inputBar}
|
||||
on:send={handleSend}
|
||||
disabled={session?.status === 'running' && $activeSession.streamingContent !== ''}
|
||||
placeholder={session?.status === 'running' ? 'Waiting for response...' : 'Type a message...'}
|
||||
placeholder={steerMode
|
||||
? 'Tell Claude what to do instead...'
|
||||
: session?.status === 'running'
|
||||
? 'Waiting for response...'
|
||||
: 'Type a message...'}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user