diff --git a/frontend/src/App.vue b/frontend/src/App.vue index d2b039e..cfa3bbc 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -308,9 +308,6 @@ onMounted(async () => { }, getMessages: () => { return responseRef.value?.getMessages() || [] - }, - move: (x: number, y: number) => { - responseRef.value?.move(x, y) } }) @@ -624,6 +621,11 @@ watch(() => route.name, (newPage) => { 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 { @@ -976,6 +978,11 @@ watch(() => route.name, (newPage) => { box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); z-index: 9998; + /* Prevent text selection and touch gestures */ + -webkit-user-select: none; + user-select: none; + -webkit-touch-callout: none; + touch-action: manipulation; } .voice-fab:hover { diff --git a/frontend/src/components/FloatingResponse.vue b/frontend/src/components/FloatingResponse.vue index 639d303..e5abbf2 100644 --- a/frontend/src/components/FloatingResponse.vue +++ b/frontend/src/components/FloatingResponse.vue @@ -1,5 +1,5 @@ diff --git a/frontend/src/components/FloatingVoice.vue b/frontend/src/components/FloatingVoice.vue index 00f11c2..9f671f6 100644 --- a/frontend/src/components/FloatingVoice.vue +++ b/frontend/src/components/FloatingVoice.vue @@ -93,83 +93,16 @@ const selectedDeviceId = ref('') const showMicSelector = ref(false) // ============ MOBILE DETECTION & AUDIO FORMAT ============ -const isMobile = ref(false) const isAndroid = ref(false) const isMobilePTT = ref(false) // Mobile push-to-talk active const supportedMimeType = ref('audio/webm;codecs=opus') -const sheetHeight = ref(45) // percentage of viewport for mobile -const isDraggingSheet = ref(false) -const sheetDragStart = ref({ y: 0, height: 0 }) -const keyboardHeight = ref(0) -const snapPoints = [25, 45, 70] // collapsed, default, expanded let mobilePTTTimeout: number | null = null function checkMobile() { const ua = navigator.userAgent - isMobile.value = window.innerWidth <= 640 || /iPhone|iPad|iPod|Android/i.test(ua) isAndroid.value = /Android/i.test(ua) } -// Virtual keyboard detection -function setupKeyboardDetection() { - if (window.visualViewport) { - window.visualViewport.addEventListener('resize', handleViewportResize) - } -} - -function handleViewportResize() { - if (!window.visualViewport || !isMobile.value) return - - const viewportHeight = window.visualViewport.height - const windowHeight = window.innerHeight - const diff = windowHeight - viewportHeight - - if (diff > 100) { - keyboardHeight.value = diff - // Auto-expand when keyboard opens - if (sheetHeight.value < 45 && isOpen.value) { - sheetHeight.value = 45 - } - } else { - keyboardHeight.value = 0 - } -} - -// Find nearest snap point -function findNearestSnap(height: number): number { - return snapPoints.reduce((prev, curr) => - Math.abs(curr - height) < Math.abs(prev - height) ? curr : prev - ) -} - -// Sheet touch handlers -function startSheetDrag(e: TouchEvent) { - if (!isMobile.value) return - const touch = e.touches[0] - if (!touch) return - - isDraggingSheet.value = true - sheetDragStart.value = { y: touch.clientY, height: sheetHeight.value } -} - -function onSheetDrag(e: TouchEvent) { - if (!isDraggingSheet.value || !isMobile.value) return - const touch = e.touches[0] - if (!touch) return - - const deltaY = sheetDragStart.value.y - touch.clientY - const deltaPercent = (deltaY / window.innerHeight) * 100 - const newHeight = sheetDragStart.value.height + deltaPercent - - sheetHeight.value = Math.max(20, Math.min(80, newHeight)) -} - -function stopSheetDrag() { - if (!isDraggingSheet.value) return - isDraggingSheet.value = false - sheetHeight.value = findNearestSnap(sheetHeight.value) -} - function detectAudioFormat(): string { // Test formats in order of preference const formats = [ @@ -307,31 +240,11 @@ function closeMicSelector(e: MouseEvent) { } const containerStyle = computed(() => { - // Mobile: bottom sheet - if (isMobile.value) { - const heightPx = keyboardHeight.value > 0 - ? `calc(${sheetHeight.value}vh - ${keyboardHeight.value}px)` - : `${sheetHeight.value}vh` - - return { - inset: 'auto 0 0 0', - width: '100%', - height: heightPx, - maxHeight: keyboardHeight.value > 0 - ? `calc(100vh - ${keyboardHeight.value}px)` - : '80vh' - } - } - - // Desktop: floating window - if (!hasCustomPosition.value) { - return { bottom: '80px', left: '16px' } - } + // Centered modal for both mobile and desktop return { - top: `${position.value.y}px`, - left: `${position.value.x}px`, - bottom: 'auto', - right: 'auto' + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)' } }) @@ -1148,12 +1061,8 @@ onMounted(async () => { document.addEventListener('keydown', handleKeyDown, { capture: true }) document.addEventListener('keyup', handleKeyUp, { capture: true }) - // Mobile detection + // Android detection for speech recognition mode checkMobile() - window.addEventListener('resize', checkMobile) - - // Virtual keyboard detection for mobile - setupKeyboardDetection() // Detect supported audio format supportedMimeType.value = detectAudioFormat() @@ -1201,10 +1110,6 @@ onBeforeUnmount(() => { document.removeEventListener('mousemove', onDrag) document.removeEventListener('mouseup', stopDrag) document.removeEventListener('click', closeMicSelector) - window.removeEventListener('resize', checkMobile) - if (window.visualViewport) { - window.visualViewport.removeEventListener('resize', handleViewportResize) - } if (holdTimeout) clearTimeout(holdTimeout) }) @@ -1254,37 +1159,21 @@ defineExpose({ diff --git a/frontend/src/services/tools/handlers/responseHandlers.ts b/frontend/src/services/tools/handlers/responseHandlers.ts index 86f0a4f..d3dfdfe 100644 --- a/frontend/src/services/tools/handlers/responseHandlers.ts +++ b/frontend/src/services/tools/handlers/responseHandlers.ts @@ -10,7 +10,6 @@ export interface ResponseControls { removeMessage: (id: string) => void clearAll: () => void getMessages: () => Array<{ id: string; message: string; type: string; timestamp: number }> - move: (x: number, y: number) => void } // Global reference to response controls (set by App.vue)