add tmux sessions
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount, onDestroy, tick } from 'svelte';
|
||||
@@ -7,23 +8,41 @@
|
||||
import InputBar from '$lib/components/InputBar.svelte';
|
||||
import PermissionRequest from '$lib/components/PermissionRequest.svelte';
|
||||
import SessionSettings from '$lib/components/SessionSettings.svelte';
|
||||
import TerminalView from '$lib/components/TerminalView.svelte';
|
||||
|
||||
$: sessionId = $page.params.id;
|
||||
|
||||
let inputBar: InputBar;
|
||||
let messageList: MessageList;
|
||||
let terminalView: TerminalView;
|
||||
let steerMode = false;
|
||||
let isEditingTitle = false;
|
||||
let editedTitle = '';
|
||||
let titleInput: HTMLInputElement;
|
||||
let menuOpen = false;
|
||||
let autoScroll = true;
|
||||
let tmuxAlive = false;
|
||||
|
||||
// Load auto-scroll preference from localStorage
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
const stored = localStorage.getItem('spiceflow-auto-scroll');
|
||||
if (stored !== null) {
|
||||
autoScroll = stored === 'true';
|
||||
}
|
||||
}
|
||||
if (sessionId) {
|
||||
activeSession.load(sessionId);
|
||||
}
|
||||
});
|
||||
|
||||
function handleToggleAutoScroll(event: CustomEvent<boolean>) {
|
||||
autoScroll = event.detail;
|
||||
if (browser) {
|
||||
localStorage.setItem('spiceflow-auto-scroll', String(autoScroll));
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
activeSession.clear();
|
||||
});
|
||||
@@ -70,7 +89,11 @@
|
||||
const newTitle = editedTitle.trim();
|
||||
isEditingTitle = false;
|
||||
if (newTitle !== (session.title || '')) {
|
||||
await activeSession.rename(newTitle);
|
||||
const result = await activeSession.rename(newTitle);
|
||||
// For tmux sessions, the ID changes on rename - navigate to new URL
|
||||
if (result?.idChanged && result.updated?.id) {
|
||||
goto(`/session/${result.updated.id}`, { replaceState: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,12 +112,17 @@
|
||||
|
||||
$: session = $activeSession.session;
|
||||
$: externalId = session?.['external-id'] || session?.externalId || '';
|
||||
$: workingDir = session?.['working-dir'] || session?.workingDir || '';
|
||||
$: shortId = externalId ? externalId.slice(0, 8) : session?.id?.slice(0, 8) || '';
|
||||
$: projectName = workingDir.split('/').pop() || '';
|
||||
$: isNewSession = !externalId && $activeSession.messages.length === 0;
|
||||
$: assistantName = session?.provider === 'opencode' ? 'OpenCode' : 'Claude';
|
||||
$: autoAcceptEdits = session?.['auto-accept-edits'] || session?.autoAcceptEdits || false;
|
||||
$: isTmuxSession = session?.provider === 'tmux';
|
||||
|
||||
const providerColors: Record<string, string> = {
|
||||
claude: 'text-spice-400',
|
||||
opencode: 'text-emerald-400',
|
||||
tmux: 'text-cyan-400'
|
||||
};
|
||||
|
||||
function handleToggleAutoAccept(event: CustomEvent<boolean>) {
|
||||
activeSession.setAutoAcceptEdits(event.detail);
|
||||
@@ -105,6 +133,15 @@
|
||||
processing: 'bg-green-500 animate-pulse',
|
||||
'awaiting-permission': 'bg-amber-500 animate-pulse'
|
||||
};
|
||||
|
||||
function handleTmuxAliveChange(event: CustomEvent<boolean>) {
|
||||
tmuxAlive = event.detail;
|
||||
}
|
||||
|
||||
// For tmux sessions, use tmuxAlive; for others, use session status
|
||||
$: statusIndicator = isTmuxSession
|
||||
? (tmuxAlive ? 'bg-green-500' : 'bg-zinc-600')
|
||||
: (statusColors[session?.status || 'idle'] || statusColors.idle);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -134,7 +171,7 @@
|
||||
{:else if session}
|
||||
<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>
|
||||
<span class="w-2 h-2 rounded-full {statusIndicator}"></span>
|
||||
{#if isEditingTitle}
|
||||
<input
|
||||
bind:this={titleInput}
|
||||
@@ -154,23 +191,19 @@
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if projectName}
|
||||
<p class="text-xs text-zinc-500 truncate">{projectName}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<SessionSettings
|
||||
{autoAcceptEdits}
|
||||
{autoScroll}
|
||||
provider={session.provider}
|
||||
on:toggleAutoAccept={handleToggleAutoAccept}
|
||||
on:toggleAutoScroll={handleToggleAutoScroll}
|
||||
on:condenseAll={() => messageList?.condenseAll()}
|
||||
/>
|
||||
|
||||
<span
|
||||
class="text-xs font-medium uppercase {session.provider === 'claude'
|
||||
? 'text-spice-400'
|
||||
: 'text-emerald-400'}"
|
||||
>
|
||||
{session.provider}
|
||||
<span class="text-xs font-medium uppercase {providerColors[session.provider] || 'text-zinc-400'}">
|
||||
{session.provider === 'tmux' ? 'terminal' : session.provider}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -194,12 +227,9 @@
|
||||
{#if session}
|
||||
<div class="px-3 py-2 border-b border-zinc-700">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full {statusColors[session.status] || statusColors.idle}"></span>
|
||||
<span class="w-2 h-2 rounded-full {statusIndicator}"></span>
|
||||
<span class="font-semibold truncate">{session.title || `Session ${shortId}`}</span>
|
||||
</div>
|
||||
{#if projectName}
|
||||
<p class="text-xs text-zinc-500 truncate mt-0.5">{projectName}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<button
|
||||
@@ -211,15 +241,26 @@
|
||||
</svg>
|
||||
Back to sessions
|
||||
</button>
|
||||
<button
|
||||
on:click={() => { menuOpen = false; messageList?.condenseAll(); }}
|
||||
class="w-full px-3 py-2 text-left text-sm hover:bg-zinc-700 flex items-center gap-2 transition-colors"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
||||
</svg>
|
||||
Condense all
|
||||
</button>
|
||||
<label class="w-full px-3 py-2 text-left text-sm hover:bg-zinc-700 flex items-center gap-2 transition-colors cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={autoScroll}
|
||||
on:change={() => { autoScroll = !autoScroll; localStorage.setItem('spiceflow-auto-scroll', String(autoScroll)); }}
|
||||
class="h-4 w-4 rounded border-zinc-600 bg-zinc-700 text-spice-500 focus:ring-spice-500 focus:ring-offset-0"
|
||||
/>
|
||||
<span>Auto-scroll</span>
|
||||
</label>
|
||||
{#if !isTmuxSession}
|
||||
<button
|
||||
on:click={() => { menuOpen = false; messageList?.condenseAll(); }}
|
||||
class="w-full px-3 py-2 text-left text-sm hover:bg-zinc-700 flex items-center gap-2 transition-colors"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
||||
</svg>
|
||||
Condense all
|
||||
</button>
|
||||
{/if}
|
||||
{#if session?.provider === 'claude'}
|
||||
<label class="w-full px-3 py-2 text-left text-sm hover:bg-zinc-700 flex items-center gap-2 transition-colors cursor-pointer border-t border-zinc-700">
|
||||
<input
|
||||
@@ -255,23 +296,11 @@
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
{:else if isTmuxSession}
|
||||
<!-- Terminal view for tmux sessions -->
|
||||
<TerminalView bind:this={terminalView} sessionId={sessionId || ''} {autoScroll} on:aliveChange={handleTmuxAliveChange} />
|
||||
{:else}
|
||||
{#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 landscape-mobile:hidden">
|
||||
<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>
|
||||
<span class="flex-1"></span>
|
||||
<button
|
||||
on:click={() => messageList?.condenseAll()}
|
||||
class="text-zinc-400 hover:text-zinc-200 transition-colors whitespace-nowrap"
|
||||
>
|
||||
condense all
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
<MessageList bind:this={messageList} messages={$activeSession.messages} streamingContent={$activeSession.streamingContent} isThinking={$activeSession.isThinking} />
|
||||
<MessageList bind:this={messageList} messages={$activeSession.messages} streamingContent={$activeSession.streamingContent} isThinking={$activeSession.isThinking} provider={session?.provider} {autoScroll} />
|
||||
|
||||
{#if $activeSession.pendingPermission}
|
||||
<PermissionRequest
|
||||
@@ -287,6 +316,7 @@
|
||||
bind:this={inputBar}
|
||||
on:send={handleSend}
|
||||
disabled={session?.status === 'processing' && $activeSession.streamingContent !== ''}
|
||||
autoFocus={true}
|
||||
placeholder={steerMode
|
||||
? `Tell ${assistantName} what to do instead...`
|
||||
: session?.status === 'processing'
|
||||
|
||||
Reference in New Issue
Block a user