feat: Add transcript-debug page with multi-agent support, hooks approval, and message selection
- Transcript debug: JSONL viewer, parsed chat view, realtime WebSocket updates, session selector - Multi-agent: ejecutor, nucleo000, and claude (global ~/.claude/projects/) with agent switcher - Hooks approval: permission/plan request forwarding via PowerShell hooks, long-poll API, UI modals - Chat features: session ID copy, select mode with checkboxes, multi-select copy, select all/deselect all - File watchers for all agent transcript directories with polling fallback on Windows
This commit is contained in:
@@ -8,6 +8,8 @@ import FloatingResponse from './components/FloatingResponse.vue'
|
||||
import FloatingVoice from './components/FloatingVoice.vue'
|
||||
import AgentBar from './components/AgentBar.vue'
|
||||
import PwaInstallBanner from './components/PwaInstallBanner.vue'
|
||||
import HooksApprovalModal from './components/HooksApprovalModal.vue'
|
||||
import { useGlobalApproval } from './composables/useGlobalApproval'
|
||||
import { initWebMCP, getWebMCP } from './services/webmcp'
|
||||
import { initTorch, destroyTorch } from './services/torch'
|
||||
import { endpoints } from './config/endpoints'
|
||||
@@ -68,6 +70,7 @@ const responseRef = ref<InstanceType<typeof FloatingResponse> | null>(null)
|
||||
const voiceRef = ref<InstanceType<typeof FloatingVoice> | null>(null)
|
||||
const canvasStore = useCanvasStore()
|
||||
const projectCanvasStore = useProjectCanvasStore()
|
||||
const { totalPending, modalVisible, connect: connectApproval, disconnect: disconnectApproval, fetchPending: fetchApprovalPending } = useGlobalApproval()
|
||||
// Voice FAB push-to-talk state
|
||||
const voicePTTActive = ref(false)
|
||||
let voiceTouchStarted = false
|
||||
@@ -264,6 +267,10 @@ onMounted(async () => {
|
||||
// Connect to WebSocket for Claude status updates
|
||||
connectStatusWs()
|
||||
|
||||
// Connect global hooks approval WS
|
||||
connectApproval()
|
||||
fetchApprovalPending()
|
||||
|
||||
// Fire torch connection early (don't await yet)
|
||||
const torchReady = initTorch()
|
||||
|
||||
@@ -349,6 +356,7 @@ onMounted(async () => {
|
||||
|
||||
onUnmounted(() => {
|
||||
destroyTorch()
|
||||
disconnectApproval()
|
||||
if (statusReconnectTimeout) clearTimeout(statusReconnectTimeout)
|
||||
if (processingTimeout) clearTimeout(processingTimeout)
|
||||
if (sessionStartTimeout) clearTimeout(sessionStartTimeout)
|
||||
@@ -398,6 +406,18 @@ watch(() => route.name, (newPage) => {
|
||||
<PwaInstallBanner />
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<button
|
||||
v-if="totalPending > 0 || modalVisible"
|
||||
class="approval-badge-btn"
|
||||
:class="{ active: modalVisible, pulse: totalPending > 0 }"
|
||||
@click="modalVisible = !modalVisible"
|
||||
title="Hooks Approval"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||
</svg>
|
||||
<span v-if="totalPending > 0" class="approval-count">{{ totalPending }}</span>
|
||||
</button>
|
||||
<button class="refresh-btn" @click="hardRefresh" title="Hard refresh (Ctrl+F5)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/>
|
||||
@@ -532,6 +552,9 @@ watch(() => route.name, (newPage) => {
|
||||
<!-- Floating Voice Input -->
|
||||
<FloatingVoice ref="voiceRef" v-model="showVoice" />
|
||||
|
||||
<!-- Global Hooks Approval Modal -->
|
||||
<HooksApprovalModal />
|
||||
|
||||
<!-- Debug Console Panel -->
|
||||
<Teleport to="body">
|
||||
<Transition name="debug-slide">
|
||||
@@ -800,6 +823,56 @@ watch(() => route.name, (newPage) => {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* Approval badge button */
|
||||
.approval-badge-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 3px;
|
||||
width: auto;
|
||||
min-width: 28px;
|
||||
height: 28px;
|
||||
padding: 0 6px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 5px;
|
||||
color: #f59e0b;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.approval-badge-btn:hover {
|
||||
background: var(--bg-hover);
|
||||
border-color: #f59e0b;
|
||||
}
|
||||
|
||||
.approval-badge-btn.active {
|
||||
background: rgba(245, 158, 11, 0.15);
|
||||
border-color: #f59e0b;
|
||||
}
|
||||
|
||||
.approval-badge-btn.pulse {
|
||||
animation: approval-badge-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.approval-count {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
padding: 0 4px;
|
||||
border-radius: 8px;
|
||||
min-width: 14px;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
@keyframes approval-badge-pulse {
|
||||
0%, 100% { box-shadow: none; }
|
||||
50% { box-shadow: 0 0 8px rgba(245, 158, 11, 0.4); }
|
||||
}
|
||||
|
||||
.app-main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
Reference in New Issue
Block a user