diff --git a/frontend/src/components/FloatingTranscriptDebug.vue b/frontend/src/components/FloatingTranscriptDebug.vue index 47e0061..24f654e 100644 --- a/frontend/src/components/FloatingTranscriptDebug.vue +++ b/frontend/src/components/FloatingTranscriptDebug.vue @@ -681,6 +681,7 @@ onBeforeUnmount(() => { @switch-agent="handleAgentSwitch" @select-session="handleSessionSelect" @create-session="handleCreateSession" + @close-session="closeTerminal" @start-recording="voice.startRecording()" @stop-recording="voice.stopRecording()" @set-voice-mode="voice.setMode($event)" diff --git a/frontend/src/components/transcript-debug/ChatContainer.vue b/frontend/src/components/transcript-debug/ChatContainer.vue index 1802c80..fcfc19c 100644 --- a/frontend/src/components/transcript-debug/ChatContainer.vue +++ b/frontend/src/components/transcript-debug/ChatContainer.vue @@ -45,6 +45,7 @@ const emit = defineEmits<{ switchAgent: [agent: AgentName] selectSession: [sessionId: string] createSession: [] + closeSession: [sessionId: string] startRecording: [] stopRecording: [] setVoiceMode: [mode: 'web' | 'whisper'] @@ -64,6 +65,30 @@ async function copySessionId() { setTimeout(() => (idCopied.value = false), 1500) } +// ── Terminal control keys ── +function sendCtrlM() { + props.terminal?.sendRaw('\x0d') +} + +function sendEsc() { + props.terminal?.sendRaw('\x1b') +} + +// ── Close session confirm ── +const closeConfirm = ref(false) +let closeConfirmTimer: ReturnType | null = null + +function handleCloseSession() { + if (!closeConfirm.value) { + closeConfirm.value = true + closeConfirmTimer = setTimeout(() => { closeConfirm.value = false }, 3000) + return + } + if (closeConfirmTimer) clearTimeout(closeConfirmTimer) + closeConfirm.value = false + emit('closeSession', props.conversation.sessionId) +} + // ── Multi-select ── const selectMode = ref(false) const selectedUuids = ref(new Set()) @@ -502,21 +527,45 @@ function formatDuration(start: string, end: string): string { {{ conversation.model }} v{{ conversation.version }} {{ conversation.messages.length }} msgs - + + + {{ formatDuration(conversation.metadata.startTime, conversation.metadata.endTime) }} + + @@ -632,6 +681,82 @@ function formatDuration(start: string, end: string): string { color: var(--accent, #6366f1); } +.key-btn { + display: inline-flex; + align-items: center; + justify-content: center; + height: 16px; + padding: 0 0.25rem; + border: none; + background: transparent; + border-radius: 3px; + cursor: pointer; + color: var(--text-muted); + flex-shrink: 0; + transition: all 0.15s; +} + +.key-btn:hover:not(:disabled) { + background: rgba(251, 191, 36, 0.15); + color: #fbbf24; +} + +.key-btn:disabled { + opacity: 0.3; + cursor: default; +} + +.key-label { + font-size: 8px; + font-weight: 600; + font-family: 'Courier New', monospace; + letter-spacing: 0.02em; +} + +.status-spacer { + flex: 1; +} + +.close-session-btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.2rem; + width: 16px; + height: 16px; + border: none; + background: transparent; + border-radius: 3px; + cursor: pointer; + color: var(--text-muted); + flex-shrink: 0; + transition: all 0.15s; +} + +.close-session-btn:hover { + background: rgba(239, 68, 68, 0.1); + color: #f87171; +} + +.close-session-btn.confirming { + width: auto; + padding: 0 0.35rem; + background: rgba(239, 68, 68, 0.15); + border: 1px solid rgba(239, 68, 68, 0.3); + color: #f87171; +} + +.close-session-btn.confirming:hover { + background: rgba(239, 68, 68, 0.25); +} + +.confirm-label { + font-size: 9px; + font-weight: 600; + font-family: 'Courier New', monospace; + white-space: nowrap; +} + .status-id { font-size: 9px; font-weight: 600; diff --git a/frontend/src/components/transcript-debug/NewSessionModal.vue b/frontend/src/components/transcript-debug/NewSessionModal.vue new file mode 100644 index 0000000..6eeaeb1 --- /dev/null +++ b/frontend/src/components/transcript-debug/NewSessionModal.vue @@ -0,0 +1,611 @@ + + +