diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 0db3609..86ff228 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -3,7 +3,6 @@ import { onMounted, onUnmounted, ref, watch } from "vue"; import { useRouter } from "vue-router"; import AppHeader from "@/components/AppHeader.vue"; import ToastNotification from "@/components/ToastNotification.vue"; -import VoiceOverlay from "@/components/VoiceOverlay.vue"; import { useTheme } from "@/composables/useTheme"; import { useShortcuts } from "@/composables/useShortcuts"; import { useAuthStore } from "@/stores/auth"; @@ -121,10 +120,6 @@ function onGlobalKeydown(e: KeyboardEvent) { router.push("/chat"); } break; - case " ": - e.preventDefault(); - document.dispatchEvent(new CustomEvent("voice:ptt-toggle")); - break; } } @@ -274,10 +269,6 @@ onUnmounted(() => { Enter New line -
- Space - Tap to speak (voice, when enabled) -
@@ -287,7 +278,6 @@ onUnmounted(() => { - diff --git a/frontend/src/components/ChatInputBar.vue b/frontend/src/components/ChatInputBar.vue index 218961e..35862ad 100644 --- a/frontend/src/components/ChatInputBar.vue +++ b/frontend/src/components/ChatInputBar.vue @@ -2,6 +2,7 @@ import { ref, computed, nextTick } from 'vue' import { apiGet, transcribeAudio } from '@/api/client' import { useVoiceRecorder } from '@/composables/useVoiceRecorder' +import { useSilenceDetector } from '@/composables/useSilenceDetector' import { useChatStore } from '@/stores/chat' import { useSettingsStore } from '@/stores/settings' import type { Note } from '@/types/note' @@ -107,21 +108,39 @@ function removeAttachedNote() { attachedNote.value = null } -// ── PTT ─────────────────────────────────────────────────────────────────────── +// ── Voice (click-to-toggle + silence detection) ───────────────────────────── const transcribingVoice = ref(false) const recorder = useVoiceRecorder() +const silenceDetector = useSilenceDetector() -async function startPtt() { - if (!voiceEnabled.value || recorder.recording.value) return - await recorder.startRecording() +async function toggleVoice() { + if (transcribingVoice.value) return + if (recorder.recording.value) { + await stopRecording() + } else { + await startRecording() + } } -async function stopPtt() { +async function startRecording() { + if (!voiceEnabled.value || recorder.recording.value) return + await recorder.startRecording() + if (recorder.error.value) return + if (recorder.stream.value) { + silenceDetector.start(recorder.stream.value, () => stopRecording()) + } +} + +async function stopRecording() { + silenceDetector.stop() if (!recorder.recording.value) return transcribingVoice.value = true try { const blob = await recorder.stopRecording() - const { transcript } = await transcribeAudio(blob) + // Pass last assistant message as context to reduce STT mishearings + const msgs = store.currentConversation?.messages ?? [] + const lastAssistant = [...msgs].reverse().find(m => m.role === 'assistant')?.content + const { transcript } = await transcribeAudio(blob, lastAssistant) if (transcript.trim()) { messageInput.value = transcript.trim() await nextTick() @@ -208,13 +227,10 @@ defineExpose({ focus, prefill }) v-if="voiceEnabled && recorder.isSupported" class="btn-icon btn-mic" :class="{ 'mic-recording': recorder.recording.value, 'mic-transcribing': transcribingVoice }" - @mousedown.prevent="startPtt" - @mouseup.prevent="stopPtt" - @touchstart.prevent="startPtt" - @touchend.prevent="stopPtt" + @click.prevent="toggleVoice" :disabled="transcribingVoice || !store.chatReady" - :title="recorder.recording.value ? 'Release to send' : 'Hold to speak'" - aria-label="Push to talk" + :title="recorder.recording.value ? 'Click to stop (or wait for silence)' : 'Click to speak'" + :aria-label="recorder.recording.value ? 'Stop recording' : 'Start recording'" > diff --git a/frontend/src/components/VoiceOverlay.vue b/frontend/src/components/VoiceOverlay.vue deleted file mode 100644 index 63e0ef6..0000000 --- a/frontend/src/components/VoiceOverlay.vue +++ /dev/null @@ -1,569 +0,0 @@ - - -
- - - -
-
- Voice - -
-
-
{{ msg.content }}
- -
- {{ streamContent }} -
-
-
-
- - -
- -
- Recording… - Transcribing… - Thinking… - Speaking… - {{ errorMsg || 'Error' }} -
-
- Tap or press Space -
- - - - - - -
- -
- - - -