diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 13502f7..cffc6e9 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -3,12 +3,10 @@ import { ref, onMounted, onUnmounted, watch } from 'vue' import { RouterView, useRoute, useRouter } from 'vue-router' import Toolbar from './components/Toolbar.vue' import TorchButton from './components/TorchButton.vue' -import FloatingTerminal from './components/FloatingTerminal.vue' import FloatingResponse from './components/FloatingResponse.vue' import { initWhisperSocket } from './services/whisperSocket' import FloatingVoice from './components/FloatingVoice.vue' import FloatingTranscriptDebug from './components/FloatingTranscriptDebug.vue' -import AgentBar from './components/AgentBar.vue' import PwaInstallBanner from './components/PwaInstallBanner.vue' import HooksApprovalModal from './components/HooksApprovalModal.vue' import { useGlobalApproval } from './composables/useGlobalApproval' @@ -16,14 +14,12 @@ import { initWebMCP, getWebMCP } from './services/webmcp' import { initTorch, destroyTorch } from './services/torch' import { endpoints } from './config/endpoints' import { initToolRegistry, activatePageTools, initToolsOnRefresh } from './services/toolRegistry' -import { setTerminalControls } from './services/tools/handlers/terminalHandlers' import { setResponseControls } from './services/tools/handlers/responseHandlers' import { useCanvasStore } from './stores/canvas' import { useProjectCanvasStore } from './stores/projectCanvas' const route = useRoute() const router = useRouter() -const showTerminal = ref(false) const showVoice = ref(false) const showTranscriptDebug = ref(false) const showDebugConsole = ref(false) @@ -68,7 +64,6 @@ function copyDebugLogs() { function clearDebugLogs() { debugLogs.value = [] } -const terminalRef = ref | null>(null) const responseRef = ref | null>(null) const voiceRef = ref | null>(null) const transcriptDebugRef = ref | null>(null) @@ -81,28 +76,8 @@ const voicePTTActive = ref(false) let voiceTouchStarted = false let voicePTTTimeout: number | null = null -// Claude status state (for FAB animations) -type ClaudeStatus = 'idle' | 'processing' | 'toolUse' | 'toolDone' | 'reading' | 'writing' | 'sessionStart' | 'subagentStart' | 'subagentStop' | 'notification' | 'permissionRequest' | 'thinking' - -const claudeStatus = ref('idle') -const claudeTool = ref(null) -const isProcessing = ref(false) // Main processing state (UserPromptSubmit → Stop) -const isReading = ref(false) // Reading files -const isWriting = ref(false) // Writing files -const hasSubagent = ref(false) // Subagent active -const awaitingPermission = ref(false) // Waiting for user permission (highest priority) -const showSessionStart = ref(false) // Session start animation (3s) -const showNotification = ref(false) // Notification pulse (2s) -const showToolFlash = ref(false) // Tool use flash (500ms) const keyboardVisible = ref(false) // Virtual keyboard visible -let statusWs: WebSocket | null = null -let statusReconnectTimeout: number | null = null -let processingTimeout: number | null = null -let sessionStartTimeout: number | null = null -let notificationTimeout: number | null = null -let toolFlashTimeout: number | null = null - function hardRefresh() { location.reload() } @@ -161,117 +136,7 @@ function handleVoiceFabTouchEnd(e: TouchEvent) { setTimeout(() => { voiceTouchStarted = false }, 100) } -function connectStatusWs() { - if (statusWs?.readyState === WebSocket.OPEN) return - - statusWs = new WebSocket(endpoints.claudeStatus) - - statusWs.onopen = () => { - console.log('[App] Status WebSocket connected') - } - - statusWs.onmessage = (event) => { - try { - const msg = JSON.parse(event.data) - if (msg.type === 'claude-status') { - const status = msg.status as ClaudeStatus - const agent = msg.agent || 'main' - console.log('[App] Claude status:', status, msg.tool, agent) - - // Only animate the main FAB for 'main' agent — other agents use AgentBar - if (agent !== 'main') return - - claudeStatus.value = status - claudeTool.value = msg.tool || null - - // Handle different status types - switch (status) { - case 'processing': - case 'thinking': - isProcessing.value = true - // Auto-reset after 2 minutes (safety) - if (processingTimeout) clearTimeout(processingTimeout) - processingTimeout = window.setTimeout(() => { - isProcessing.value = false - }, 120000) - break - - case 'idle': - isProcessing.value = false - isReading.value = false - isWriting.value = false - awaitingPermission.value = false - if (processingTimeout) clearTimeout(processingTimeout) - break - - case 'permissionRequest': - awaitingPermission.value = true - break - - case 'reading': - isReading.value = true - triggerToolFlash() - break - - case 'writing': - isWriting.value = true - triggerToolFlash() - break - - case 'toolUse': - triggerToolFlash() - break - - case 'toolDone': - isReading.value = false - isWriting.value = false - awaitingPermission.value = false - break - - case 'sessionStart': - showSessionStart.value = true - if (sessionStartTimeout) clearTimeout(sessionStartTimeout) - sessionStartTimeout = window.setTimeout(() => { - showSessionStart.value = false - }, 3000) - break - - case 'subagentStart': - hasSubagent.value = true - break - - case 'subagentStop': - hasSubagent.value = false - break - - case 'notification': - showNotification.value = true - if (notificationTimeout) clearTimeout(notificationTimeout) - notificationTimeout = window.setTimeout(() => { - showNotification.value = false - }, 2000) - break - } - } - - } catch { /* ignore non-JSON messages */ } - } - - statusWs.onclose = () => { - isProcessing.value = false - statusReconnectTimeout = window.setTimeout(connectStatusWs, 2000) - } -} - -function triggerToolFlash() { - showToolFlash.value = true - if (toolFlashTimeout) clearTimeout(toolFlashTimeout) - toolFlashTimeout = window.setTimeout(() => { - showToolFlash.value = false - }, 500) -} - -type PageName = 'home' | 'canvas' | 'components' | 'themes' | 'project-canvas' | 'database' | 'source' | 'terminal' | 'tools' | 'agents' +type PageName = 'home' | 'canvas' | 'components' | 'themes' | 'project-canvas' | 'database' | 'source' | 'tools' | 'agents' function syncThemeColor() { const bg = getComputedStyle(document.documentElement).getPropertyValue('--bg-primary').trim() @@ -284,9 +149,6 @@ onMounted(async () => { // Sync Windows titlebar color with CSS variable syncThemeColor() - // Connect to WebSocket for Claude status updates - connectStatusWs() - // Connect global hooks approval WS connectApproval() fetchApprovalPending() @@ -307,43 +169,6 @@ onMounted(async () => { const currentPage = (route.name as string) || 'canvas' initToolsOnRefresh(currentPage as PageName) - // Setup terminal controls for MCP tools - setTerminalControls({ - open: (x?: number, y?: number) => { - if (terminalRef.value) { - terminalRef.value.open(x, y) - } else { - showTerminal.value = true - } - }, - close: () => { - if (terminalRef.value) { - terminalRef.value.close() - } else { - showTerminal.value = false - } - }, - toggle: () => { - if (terminalRef.value) { - terminalRef.value.toggle() - } else { - showTerminal.value = !showTerminal.value - } - }, - move: (x: number, y: number) => { - terminalRef.value?.move(x, y) - }, - resize: (w: number, h: number) => { - terminalRef.value?.resize(w, h) - }, - getState: () => { - if (terminalRef.value) { - return terminalRef.value.getState() - } - return { isOpen: showTerminal.value, position: { x: 0, y: 0 }, size: { w: 580, h: 360 } } - } - }) - // Setup response controls for MCP tools setResponseControls({ addMessage: (message: string, type?: 'info' | 'success' | 'warning' | 'error') => { @@ -388,12 +213,6 @@ onUnmounted(() => { document.removeEventListener('keydown', handleGlobalKeydown) destroyTorch() disconnectApproval() - if (statusReconnectTimeout) clearTimeout(statusReconnectTimeout) - if (processingTimeout) clearTimeout(processingTimeout) - if (sessionStartTimeout) clearTimeout(sessionStartTimeout) - if (notificationTimeout) clearTimeout(notificationTimeout) - if (toolFlashTimeout) clearTimeout(toolFlashTimeout) - statusWs?.close() }) // Watch for route changes and update tools @@ -468,94 +287,10 @@ watch(() => route.name, (newPage) => { - - - - - - - - - @@ -951,368 +680,6 @@ watch(() => route.name, (newPage) => { transform: translateX(-20px); } -/* Terminal FAB - Claude Life */ -.terminal-fab { - position: fixed; - bottom: 20px; - right: 20px; - width: 58px; - height: 58px; - border-radius: 18px; - background: linear-gradient(145deg, #7c3aed 0%, #6366f1 50%, #8b5cf6 100%); - color: white; - border: 2px solid rgba(255, 255, 255, 0.15); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - box-shadow: - 0 4px 15px rgba(124, 58, 237, 0.4), - 0 8px 30px rgba(99, 102, 241, 0.3), - inset 0 1px 0 rgba(255, 255, 255, 0.2); - transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); - z-index: 9998; - overflow: visible; - backdrop-filter: blur(10px); - /* Prevent text selection and touch gestures */ - -webkit-user-select: none; - user-select: none; - -webkit-touch-callout: none; - touch-action: manipulation; -} - -.terminal-fab::before { - content: ''; - position: absolute; - inset: -3px; - border-radius: 21px; - background: linear-gradient(145deg, rgba(139, 92, 246, 0.5), rgba(99, 102, 241, 0.2)); - z-index: -1; - opacity: 0; - transition: opacity 0.3s ease; -} - -.terminal-fab:hover { - transform: translateY(-3px) scale(1.05); - box-shadow: - 0 8px 25px rgba(124, 58, 237, 0.5), - 0 15px 40px rgba(99, 102, 241, 0.35), - inset 0 1px 0 rgba(255, 255, 255, 0.25); -} - -.terminal-fab:hover::before { - opacity: 1; -} - -/* Nucleo atom icon animation */ -.nucleo-icon { - animation: nucleo-orbit 8s linear infinite; - filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.6)); -} - -.nucleo-icon ellipse { - transform-origin: center; -} - -/* Terminal active - Connected state */ -.terminal-fab.active { - background: linear-gradient(145deg, #6366f1 0%, #4f46e5 50%, #7c3aed 100%); - border-color: rgba(16, 185, 129, 0.5); - box-shadow: - 0 4px 15px rgba(99, 102, 241, 0.4), - 0 8px 30px rgba(79, 70, 229, 0.3), - 0 0 20px rgba(16, 185, 129, 0.2), - inset 0 1px 0 rgba(255, 255, 255, 0.2); - transform: none; -} - -.terminal-fab.active:hover { - transform: translateY(-2px); - box-shadow: - 0 6px 20px rgba(99, 102, 241, 0.5), - 0 12px 35px rgba(79, 70, 229, 0.35), - 0 0 25px rgba(16, 185, 129, 0.3), - inset 0 1px 0 rgba(255, 255, 255, 0.25); -} - -/* Connection indicator dot */ -.connection-dot { - position: absolute; - top: 8px; - right: 8px; - width: 10px; - height: 10px; - background: #10b981; - border-radius: 50%; - border: 2px solid rgba(255, 255, 255, 0.9); - box-shadow: 0 0 8px rgba(16, 185, 129, 0.8); - animation: connection-pulse 2s ease-in-out infinite; -} - -/* Minimize icon */ -.minimize-icon { - opacity: 0.9; - transition: transform 0.2s ease; -} - -.terminal-fab.active:hover .minimize-icon { - transform: translateY(2px); -} - -/* Processing state (UserPromptSubmit → Stop) - Orange pulsing */ -.terminal-fab.processing { - background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%) !important; - box-shadow: 0 8px 24px rgba(245, 158, 11, 0.4) !important; - animation: processing-pulse 2s ease-in-out infinite !important; - transform: rotate(0deg) !important; -} - -.terminal-fab.processing:hover { - box-shadow: 0 12px 32px rgba(245, 158, 11, 0.5) !important; - transform: scale(1.05) !important; -} - -/* Reading state - Cyan scanning */ -.terminal-fab.reading { - background: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%) !important; - box-shadow: 0 8px 24px rgba(6, 182, 212, 0.4) !important; - animation: reading-scan 1.5s ease-in-out infinite !important; -} - -/* Writing state - Green pulse */ -.terminal-fab.writing { - background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important; - box-shadow: 0 8px 24px rgba(16, 185, 129, 0.4) !important; - animation: writing-pulse 0.8s ease-in-out infinite !important; -} - -/* Subagent active - Purple with orbital ring */ -.terminal-fab.subagent { - box-shadow: 0 8px 24px rgba(139, 92, 246, 0.5) !important; -} - -/* Session start - Green wake-up effect */ -.terminal-fab.session-start { - animation: session-wake 3s ease-out forwards !important; -} - -/* Notification - Yellow bounce */ -.terminal-fab.notification { - animation: notification-bounce 0.5s ease-in-out 4 !important; -} - -/* Permission Request - HIGHEST PRIORITY - Red pulsing alert */ -.terminal-fab.permission { - background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%) !important; - box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7) !important; - animation: permission-pulse 1s ease-in-out infinite !important; - transform: rotate(0deg) scale(1.1) !important; - z-index: 10000 !important; -} - -.terminal-fab.permission:hover { - transform: scale(1.15) !important; -} - -/* Permission icon animation */ -.permission-icon { - animation: permission-shake 0.5s ease-in-out infinite; - filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.8)); -} - -/* Tool flash - Quick white flash */ -.terminal-fab.tool-flash::after { - content: ''; - position: absolute; - inset: -4px; - border-radius: 20px; - background: rgba(255, 255, 255, 0.4); - animation: tool-flash 0.5s ease-out forwards; - pointer-events: none; -} - -/* Thinking dots animation */ -.thinking-dots { - display: flex; - align-items: center; - justify-content: center; - gap: 4px; -} - -.thinking-dots span { - width: 6px; - height: 6px; - background: white; - border-radius: 50%; - animation: thinking-dot 1.4s ease-in-out infinite; -} - -.thinking-dots span:nth-child(1) { animation-delay: 0s; } -.thinking-dots span:nth-child(2) { animation-delay: 0.2s; } -.thinking-dots span:nth-child(3) { animation-delay: 0.4s; } - -/* Status icons */ -.status-icon { - animation: icon-breathe 1s ease-in-out infinite; -} - -/* Orbital ring for subagent */ -.orbital-ring { - position: absolute; - width: 80px; - height: 80px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - animation: orbit-spin 3s linear infinite; - color: rgba(139, 92, 246, 0.8); - pointer-events: none; -} - -/* Session start ripples */ -.session-ripples { - position: absolute; - inset: 0; - pointer-events: none; -} - -.session-ripples span { - position: absolute; - inset: -10px; - border: 2px solid rgba(16, 185, 129, 0.6); - border-radius: 20px; - animation: ripple-expand 3s ease-out forwards; -} - -.session-ripples span:nth-child(1) { animation-delay: 0s; } -.session-ripples span:nth-child(2) { animation-delay: 0.5s; } -.session-ripples span:nth-child(3) { animation-delay: 1s; } - -/* Keyframes */ -@keyframes thinking-dot { - 0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; } - 40% { transform: scale(1); opacity: 1; } -} - -@keyframes processing-pulse { - 0%, 100% { box-shadow: 0 8px 24px rgba(245, 158, 11, 0.4); } - 50% { box-shadow: 0 8px 40px rgba(245, 158, 11, 0.7); } -} - -@keyframes reading-scan { - 0%, 100% { - box-shadow: 0 8px 24px rgba(6, 182, 212, 0.4); - transform: rotate(0deg); - } - 25% { transform: rotate(-2deg); } - 75% { transform: rotate(2deg); } - 50% { - box-shadow: 0 8px 40px rgba(6, 182, 212, 0.7); - } -} - -@keyframes writing-pulse { - 0%, 100% { - transform: scale(1); - box-shadow: 0 8px 24px rgba(16, 185, 129, 0.4); - } - 50% { - transform: scale(1.05); - box-shadow: 0 8px 32px rgba(16, 185, 129, 0.6); - } -} - -@keyframes session-wake { - 0% { - transform: scale(0.8); - opacity: 0.5; - box-shadow: 0 0 0 rgba(16, 185, 129, 0); - } - 30% { - transform: scale(1.15); - box-shadow: 0 0 60px rgba(16, 185, 129, 0.6); - } - 60% { transform: scale(0.95); } - 100% { - transform: scale(1); - opacity: 1; - box-shadow: 0 8px 24px rgba(99, 102, 241, 0.4); - } -} - -@keyframes notification-bounce { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-8px); } -} - -@keyframes tool-flash { - 0% { opacity: 1; transform: scale(1); } - 100% { opacity: 0; transform: scale(1.3); } -} - -@keyframes icon-breathe { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.7; } -} - -@keyframes orbit-spin { - from { transform: translate(-50%, -50%) rotate(0deg); } - to { transform: translate(-50%, -50%) rotate(360deg); } -} - -@keyframes ripple-expand { - 0% { - transform: scale(1); - opacity: 1; - } - 100% { - transform: scale(2); - opacity: 0; - } -} - -@keyframes permission-pulse { - 0%, 100% { - box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7), 0 8px 24px rgba(239, 68, 68, 0.5); - transform: scale(1.1); - } - 50% { - box-shadow: 0 0 0 15px rgba(239, 68, 68, 0), 0 8px 40px rgba(239, 68, 68, 0.8); - transform: scale(1.15); - } -} - -@keyframes permission-shake { - 0%, 100% { transform: rotate(0deg); } - 25% { transform: rotate(-5deg); } - 75% { transform: rotate(5deg); } -} - -@keyframes nucleo-orbit { - 0% { - transform: rotate(0deg); - filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.6)); - } - 50% { - filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.9)); - } - 100% { - transform: rotate(360deg); - filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.6)); - } -} - -@keyframes connection-pulse { - 0%, 100% { - box-shadow: 0 0 8px rgba(16, 185, 129, 0.8); - transform: scale(1); - } - 50% { - box-shadow: 0 0 12px rgba(16, 185, 129, 1), 0 0 20px rgba(16, 185, 129, 0.4); - transform: scale(1.1); - } -} - /* Voice FAB */ .voice-fab { position: fixed; @@ -1408,30 +775,6 @@ watch(() => route.name, (newPage) => { } @media (max-width: 768px) { - .terminal-fab { - bottom: 80px; - right: 16px; - width: 54px; - height: 54px; - border-radius: 16px; - } - - .terminal-fab::before { - border-radius: 19px; - } - - .connection-dot { - width: 8px; - height: 8px; - top: 6px; - right: 6px; - } - - .orbital-ring { - width: 70px; - height: 70px; - } - .voice-fab { bottom: 80px; left: 16px; @@ -1449,26 +792,22 @@ watch(() => route.name, (newPage) => { /* Mobile: FABs above bottom sheets */ @media (max-width: 1024px) and (pointer: coarse) { - .terminal-fab, .voice-fab, .transcript-fab { z-index: 10001; transition: bottom 0.25s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s ease; } - .terminal-fab.sheet-open, .voice-fab.sheet-open, .transcript-fab.sheet-open { bottom: calc(15vh + 100px); } - .terminal-fab.keyboard-visible, .voice-fab.keyboard-visible, .transcript-fab.keyboard-visible { bottom: 35vh; } - .terminal-fab.keyboard-visible.sheet-open, .voice-fab.keyboard-visible.sheet-open, .transcript-fab.keyboard-visible.sheet-open { bottom: 45vh; diff --git a/frontend/src/components/Toolbar.vue b/frontend/src/components/Toolbar.vue index b46ee8f..2c2696e 100644 --- a/frontend/src/components/Toolbar.vue +++ b/frontend/src/components/Toolbar.vue @@ -104,13 +104,6 @@ onMounted(() => { - - - - - - - diff --git a/frontend/src/components/ToolsDropdown.vue b/frontend/src/components/ToolsDropdown.vue index 6d4a94a..3700d16 100644 --- a/frontend/src/components/ToolsDropdown.vue +++ b/frontend/src/components/ToolsDropdown.vue @@ -21,8 +21,7 @@ const categoryTools: Record = { theme: ['get_design_tokens', 'get_active_theme', 'set_theme_variable', 'save_theme', 'list_themes', 'switch_theme', 'reset_theme'], database: ['list_tables', 'get_table_schema', 'get_table_data', 'get_database_stats', 'execute_query'], source: ['get_repo_info', 'list_repo_files', 'read_repo_file', 'search_repo_code'], - project: ['list_canvases', 'create_canvas', 'get_canvas', 'update_canvas', 'delete_canvas', 'clone_canvas', 'add_component_to_canvas', 'remove_component_from_canvas', 'get_canvas_components'], - terminal: ['terminal_open', 'terminal_close', 'terminal_toggle', 'terminal_move', 'terminal_resize'] + project: ['list_canvases', 'create_canvas', 'get_canvas', 'update_canvas', 'delete_canvas', 'clone_canvas', 'add_component_to_canvas', 'remove_component_from_canvas', 'get_canvas_components'] } const categories = computed(() => { diff --git a/frontend/src/components/transcript-debug/UserInput.vue b/frontend/src/components/transcript-debug/UserInput.vue index 0da9f09..3984410 100644 --- a/frontend/src/components/transcript-debug/UserInput.vue +++ b/frontend/src/components/transcript-debug/UserInput.vue @@ -1,5 +1,5 @@