From f9b5ad3db6f9a8ec428c867085a58a14c0cbf0cd Mon Sep 17 00:00:00 2001 From: josedario87 Date: Sat, 14 Feb 2026 04:51:50 -0600 Subject: [PATCH] feat: Push-to-talk on voice FAB button - Hold FAB to open panel and start recording immediately - Release to stop recording and send after 1s buffer - Orange pulsing animation when PTT active - PTT also works on record button inside modal - Added stopRecordingAndSend exposed method --- .claude/settings.local.json | 125 ++++++++++---------- frontend/src/App.vue | 69 ++++++++++- frontend/src/components/FloatingVoice.vue | 138 +++++++++++++++++++++- server/whisper_server.py | 2 +- 4 files changed, 263 insertions(+), 71 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index cfb8468..56b1e0e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,4 +1,67 @@ { + "permissions": { + "allow": [ + "Skill(playwright-cli)", + "Bash(playwright-cli close:*)", + "Bash(playwright-cli open:*)", + "Bash(playwright-cli screenshot:*)", + "WebFetch(domain:github.com)", + "Bash(playwright-cli goto:*)", + "Bash(playwright-cli snapshot:*)", + "Bash(playwright-cli mousewheel:*)", + "Bash(playwright-cli eval:*)", + "Bash(playwright-cli tab-list:*)", + "Bash(playwright-cli tab-select:*)", + "Bash(playwright-cli click:*)", + "Bash(playwright-cli press:*)", + "WebFetch(domain:gitea.nucleoriofrio.com)", + "Bash(dir \"C:\\\\Users\\\\jodar\\\\agent-ui\")", + "WebSearch", + "Bash(cmd /c \"bun --version\")", + "Bash(powershell -Command \"bun --version\")", + "Bash(C:Usersjodar.bunbinbun.exe create vite . --template vue-ts)", + "mcp__agent-ui___webmcp_get-token", + "mcp__agent-ui___webmcp_quitar-tool", + "mcp__agent-ui__localhost_3000-render_html", + "mcp__agent-ui__localhost_4100-navigate_to", + "mcp__agent-ui__localhost_4100-get_design_tokens", + "mcp__agent-ui__localhost_4100-set_theme_variable", + "mcp__agent-ui__localhost_4100-list_available_tools", + "mcp__agent-ui__localhost_4100-switch_theme", + "mcp__agent-ui__localhost_4100-set_default_theme", + "mcp__agent-ui__localhost_4100-save_theme", + "mcp__agent-ui___webmcp_browser-info", + "mcp__agent-ui__localhost_4100-render_vue_component", + "Bash(bun remove:*)", + "Bash(bun add:*)", + "mcp__agent-ui__localhost_4100-confetti", + "mcp__agent-ui__localhost_4100-get_current_page", + "mcp__agent-ui___webmcp_server-info", + "mcp__agent-ui__localhost_4100-toggle_pin_tool", + "mcp__agent-ui__localhost_4100-pin_tool", + "Bash(npx vue-tsc:*)", + "mcp__agent-ui__localhost_4100-activate_tool", + "mcp__agent-ui__localhost_4100-terminal_open", + "mcp__agent-ui__localhost_4100-terminal_move", + "mcp__agent-ui__localhost_4100-terminal_resize", + "mcp__agent-ui__localhost_4100-terminal_toggle", + "mcp__agent-ui__localhost_4100-terminal_close", + "mcp__agent-ui__localhost_4100-bubbleResponse", + "mcp__agent-ui__localhost_4100-notificar", + "mcp__agent-ui__localhost_4100-enviar_al_panel", + "mcp__agent-ui__localhost_4100-render_html", + "mcp__agent-ui__localhost_4100-load_vue_component", + "mcp__agent-ui__localhost_4100-page_refresh", + "WebFetch(domain:docs.anthropic.com)", + "mcp__agent-ui__z590_nucleoriofrio_com-bubbleResponse", + "Bash(git add:*)", + "Bash(git commit:*)" + ] + }, + "enableAllProjectMcpServers": true, + "enabledMcpjsonServers": [ + "agent-ui" + ], "hooks": { "UserPromptSubmit": [ { @@ -125,65 +188,5 @@ ] } ] - }, - "permissions": { - "allow": [ - "Skill(playwright-cli)", - "Bash(playwright-cli close:*)", - "Bash(playwright-cli open:*)", - "Bash(playwright-cli screenshot:*)", - "WebFetch(domain:github.com)", - "Bash(playwright-cli goto:*)", - "Bash(playwright-cli snapshot:*)", - "Bash(playwright-cli mousewheel:*)", - "Bash(playwright-cli eval:*)", - "Bash(playwright-cli tab-list:*)", - "Bash(playwright-cli tab-select:*)", - "Bash(playwright-cli click:*)", - "Bash(playwright-cli press:*)", - "WebFetch(domain:gitea.nucleoriofrio.com)", - "Bash(dir \"C:\\\\Users\\\\jodar\\\\agent-ui\")", - "WebSearch", - "Bash(cmd /c \"bun --version\")", - "Bash(powershell -Command \"bun --version\")", - "Bash(C:Usersjodar.bunbinbun.exe create vite . --template vue-ts)", - "mcp__agent-ui___webmcp_get-token", - "mcp__agent-ui___webmcp_quitar-tool", - "mcp__agent-ui__localhost_3000-render_html", - "mcp__agent-ui__localhost_4100-navigate_to", - "mcp__agent-ui__localhost_4100-get_design_tokens", - "mcp__agent-ui__localhost_4100-set_theme_variable", - "mcp__agent-ui__localhost_4100-list_available_tools", - "mcp__agent-ui__localhost_4100-switch_theme", - "mcp__agent-ui__localhost_4100-set_default_theme", - "mcp__agent-ui__localhost_4100-save_theme", - "mcp__agent-ui___webmcp_browser-info", - "mcp__agent-ui__localhost_4100-render_vue_component", - "Bash(bun remove:*)", - "Bash(bun add:*)", - "mcp__agent-ui__localhost_4100-confetti", - "mcp__agent-ui__localhost_4100-get_current_page", - "mcp__agent-ui___webmcp_server-info", - "mcp__agent-ui__localhost_4100-toggle_pin_tool", - "mcp__agent-ui__localhost_4100-pin_tool", - "Bash(npx vue-tsc:*)", - "mcp__agent-ui__localhost_4100-activate_tool", - "mcp__agent-ui__localhost_4100-terminal_open", - "mcp__agent-ui__localhost_4100-terminal_move", - "mcp__agent-ui__localhost_4100-terminal_resize", - "mcp__agent-ui__localhost_4100-terminal_toggle", - "mcp__agent-ui__localhost_4100-terminal_close", - "mcp__agent-ui__localhost_4100-bubbleResponse", - "mcp__agent-ui__localhost_4100-notificar", - "mcp__agent-ui__localhost_4100-enviar_al_panel", - "mcp__agent-ui__localhost_4100-render_html", - "mcp__agent-ui__localhost_4100-load_vue_component", - "mcp__agent-ui__localhost_4100-page_refresh", - "WebFetch(domain:docs.anthropic.com)" - ] - }, - "enableAllProjectMcpServers": true, - "enabledMcpjsonServers": [ - "agent-ui" - ] + } } diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 4128f4d..d2b039e 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -63,8 +63,14 @@ function clearDebugLogs() { } const terminalRef = ref | null>(null) const responseRef = ref | null>(null) +const voiceRef = ref | null>(null) const canvasStore = useCanvasStore() +// Voice FAB push-to-talk state +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' @@ -90,6 +96,45 @@ function hardRefresh() { location.reload() } +// Voice FAB push-to-talk handlers +function handleVoiceFabClick() { + // If touch just ended, ignore click + if (voiceTouchStarted) { + voiceTouchStarted = false + return + } + // Normal click: toggle panel + showVoice.value = !showVoice.value +} + +function handleVoiceFabTouchStart(e: TouchEvent) { + e.preventDefault() + voiceTouchStarted = true + voicePTTActive.value = true + + // Open panel and start recording + showVoice.value = true + + // Wait a moment for panel to open, then start recording + setTimeout(() => { + voiceRef.value?.startRecording() + }, 100) +} + +function handleVoiceFabTouchEnd(e: TouchEvent) { + e.preventDefault() + + if (!voicePTTActive.value) return + + // Add buffer before stopping + voicePTTTimeout = window.setTimeout(() => { + voiceRef.value?.stopRecordingAndSend() + voicePTTActive.value = false + }, 1000) + + setTimeout(() => { voiceTouchStarted = false }, 100) +} + function connectStatusWs() { if (statusWs?.readyState === WebSocket.OPEN) return @@ -418,9 +463,12 @@ watch(() => route.name, (newPage) => {