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:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
|||||||
@@ -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...'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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' }
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user