feat: Add page_refresh global tool and update voice shortcut to Ctrl+Space

- Add page_refresh tool to reload the page via MCP
- Change push-to-talk shortcut from Ctrl+S to Ctrl+Space
- Use capture phase for keyboard events to intercept before terminal
This commit is contained in:
2026-02-13 21:41:56 -06:00
parent 8ddf5dc4f3
commit e867b7873e
4 changed files with 37 additions and 17 deletions

View File

@@ -47,7 +47,7 @@ const displayText = computed(() => {
if (interimTranscript.value) { if (interimTranscript.value) {
return transcript.value + ' ' + interimTranscript.value return transcript.value + ' ' + interimTranscript.value
} }
return transcript.value || 'Presiona el micrófono o mantén Ctrl+S...' return transcript.value || 'Presiona el micrófono o mantén Ctrl+Space...'
}) })
const containerStyle = computed(() => { const containerStyle = computed(() => {
@@ -252,10 +252,11 @@ function stopDrag() {
document.removeEventListener('mouseup', stopDrag) document.removeEventListener('mouseup', stopDrag)
} }
// Keyboard shortcut handlers (Ctrl+S) // Keyboard shortcut handlers (Ctrl+Space)
function handleKeyDown(e: KeyboardEvent) { function handleKeyDown(e: KeyboardEvent) {
if (e.ctrlKey && e.key.toLowerCase() === 's') { if (e.ctrlKey && e.key === ' ') {
e.preventDefault() e.preventDefault()
e.stopImmediatePropagation() // Prevent terminal and other handlers from receiving
// Ignore if already holding // Ignore if already holding
if (keyDownTime > 0) return if (keyDownTime > 0) return
@@ -267,19 +268,21 @@ function handleKeyDown(e: KeyboardEvent) {
isOpen.value = true isOpen.value = true
} }
// Start recording after 500ms hold // Start recording after 150ms hold
holdTimeout = window.setTimeout(() => { holdTimeout = window.setTimeout(() => {
if (keyDownTime > 0 && !isRecording.value) { if (keyDownTime > 0 && !isRecording.value) {
isPushToTalk.value = true isPushToTalk.value = true
startRecording() startRecording()
} }
}, 500) }, 150)
} }
} }
function handleKeyUp(e: KeyboardEvent) { function handleKeyUp(e: KeyboardEvent) {
// Only react to 's' key release, ignore Control // Only react to Space release when Ctrl+Space was pressed
if (e.key.toLowerCase() === 's' && keyDownTime > 0) { if (e.key === ' ' && keyDownTime > 0) {
e.preventDefault()
e.stopImmediatePropagation() // Prevent terminal and other handlers from receiving
console.log('[Voice] Key S released, isPushToTalk:', isPushToTalk.value, 'isRecording:', isRecording.value) console.log('[Voice] Key S released, isPushToTalk:', isPushToTalk.value, 'isRecording:', isRecording.value)
if (holdTimeout) { if (holdTimeout) {
@@ -287,9 +290,9 @@ function handleKeyUp(e: KeyboardEvent) {
holdTimeout = null holdTimeout = null
} }
// If was push-to-talk recording, stop and send after 500ms // If was push-to-talk recording, stop and send after 1200ms
if (isPushToTalk.value && isRecording.value) { if (isPushToTalk.value && isRecording.value) {
console.log('[Voice] Stopping recording, will send in 500ms') console.log('[Voice] Stopping recording, will send in 1200ms')
stopRecording() stopRecording()
setTimeout(() => { setTimeout(() => {
console.log('[Voice] Sending transcript:', transcript.value.trim()) console.log('[Voice] Sending transcript:', transcript.value.trim())
@@ -301,7 +304,7 @@ function handleKeyUp(e: KeyboardEvent) {
isPushToTalk.value = false isPushToTalk.value = false
close() close()
} }
}, 500) }, 1200)
} }
keyDownTime = 0 keyDownTime = 0
@@ -348,16 +351,17 @@ function sendTranscriptAndClose() {
onMounted(() => { onMounted(() => {
recognition = initRecognition() recognition = initRecognition()
document.addEventListener('keydown', handleKeyDown) // Use capture phase to intercept before terminal or other elements
document.addEventListener('keyup', handleKeyUp) document.addEventListener('keydown', handleKeyDown, { capture: true })
document.addEventListener('keyup', handleKeyUp, { capture: true })
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
stopRecording() stopRecording()
recognition = null recognition = null
disconnectSocket() disconnectSocket()
document.removeEventListener('keydown', handleKeyDown) document.removeEventListener('keydown', handleKeyDown, { capture: true })
document.removeEventListener('keyup', handleKeyUp) document.removeEventListener('keyup', handleKeyUp, { capture: true })
document.removeEventListener('mousemove', onDrag) document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', stopDrag) document.removeEventListener('mouseup', stopDrag)
if (holdTimeout) clearTimeout(holdTimeout) if (holdTimeout) clearTimeout(holdTimeout)
@@ -421,7 +425,7 @@ defineExpose({
<span class="final">{{ transcript }}</span> <span class="final">{{ transcript }}</span>
<span class="interim">{{ interimTranscript }}</span> <span class="interim">{{ interimTranscript }}</span>
<span v-if="!transcript && !interimTranscript" class="placeholder"> <span v-if="!transcript && !interimTranscript" class="placeholder">
Presiona el micrófono o mantén Ctrl+S... Presiona el micrófono o mantén Ctrl+Space...
</span> </span>
</div> </div>

View File

@@ -127,7 +127,7 @@ function getToolConfigs(): Map<string, ToolConfig> {
// Category to tool names mapping // Category to tool names mapping
const categoryTools: Record<ToolCategory, string[]> = { const categoryTools: Record<ToolCategory, string[]> = {
global: ['get_current_page', 'navigate_to', 'list_available_tools', 'activate_tool', 'deactivate_tool', 'pin_tool'], global: ['get_current_page', 'navigate_to', 'list_available_tools', 'activate_tool', 'deactivate_tool', 'pin_tool', 'page_refresh'],
canvas: ['render_html', 'render_vue_component'], canvas: ['render_html', 'render_vue_component'],
component: ['save_vue_component', 'load_vue_component', 'list_vue_components', 'delete_vue_component'], component: ['save_vue_component', 'load_vue_component', 'list_vue_components', 'delete_vue_component'],
theme: ['get_design_tokens', 'get_active_theme', 'set_theme_variable', 'save_theme', 'list_themes', 'switch_theme', 'reset_theme'], theme: ['get_design_tokens', 'get_active_theme', 'set_theme_variable', 'save_theme', 'list_themes', 'switch_theme', 'reset_theme'],

View File

@@ -208,6 +208,21 @@ export function createGlobalHandlers(callbacks: ToolManagementCallbacks): ToolCo
return `Herramienta "${args.tool_name}" pinneada. Permanecera activa al cambiar de pagina.` return `Herramienta "${args.tool_name}" pinneada. Permanecera activa al cambiar de pagina.`
} }
},
{
name: 'page_refresh',
description: 'Recarga la pagina completamente.',
category: 'global',
schema: {
type: 'object',
properties: {}
},
handler: () => {
setTimeout(() => {
window.location.reload()
}, 100)
return 'Recargando pagina...'
}
} }
] ]
} }

View File

@@ -15,6 +15,7 @@ export const ALL_TOOL_METAS: ToolMeta[] = [
{ name: 'activate_tool', description: 'Activa una herramienta MCP', category: 'global' }, { name: 'activate_tool', description: 'Activa una herramienta MCP', category: 'global' },
{ name: 'deactivate_tool', description: 'Desactiva una herramienta MCP', category: 'global' }, { name: 'deactivate_tool', description: 'Desactiva una herramienta MCP', category: 'global' },
{ name: 'pin_tool', description: 'Pinnea una herramienta', category: 'global' }, { name: 'pin_tool', description: 'Pinnea una herramienta', category: 'global' },
{ name: 'page_refresh', description: 'Recarga la pagina completamente', category: 'global' },
// Canvas tools // Canvas tools
{ name: 'render_html', description: 'Renderiza HTML en el canvas', category: 'canvas' }, { name: 'render_html', description: 'Renderiza HTML en el canvas', category: 'canvas' },
@@ -65,7 +66,7 @@ export const ALL_TOOL_METAS: ToolMeta[] = [
{ name: 'terminal_toggle', description: 'Alterna abrir/cerrar el terminal', category: 'terminal' }, { name: 'terminal_toggle', description: 'Alterna abrir/cerrar el terminal', category: 'terminal' },
{ name: 'terminal_move', description: 'Mueve la ventana del terminal a una posicion', category: 'terminal' }, { name: 'terminal_move', description: 'Mueve la ventana del terminal a una posicion', category: 'terminal' },
{ name: 'terminal_resize', description: 'Cambia el tamano de la ventana del terminal', category: 'terminal' }, { name: 'terminal_resize', description: 'Cambia el tamano de la ventana del terminal', category: 'terminal' },
// Response UI tools // Response UI tools
{ name: 'bubbleResponse', description: 'Muestra un mensaje del agente en la UI', category: 'terminal' } { name: 'bubbleResponse', description: 'Muestra un mensaje del agente en la UI', category: 'terminal' }
] ]