fix: propagate send failures, guard image conversion, cleanup speech mic (#130)
Seven correctness bugs in the session composer: - sessions.ts sendMessage: await the prompt submission and rethrow on failure instead of a fire-and-forget .catch(), so handleSend's existing restore-draft-and-alert catch actually runs. - pasteFromClipboard: route pasted images through toJpeg() so they get the same resize/compress treatment as picked/captured photos. - pickFromLibrary/pickFromCamera: wrap toJpeg() in try/catch (and switch to Promise.allSettled for the multi-select batch) so one bad asset doesn't silently drop the whole batch; surface a new imageFailed alert. - pickFromLibrary: cap selection at 10 images. - useSpeech: abort the native recognition session on unmount so the mic doesn't stay hot after leaving the screen. - Surface useSpeech's error via Alert, keyed on the error value so it fires once per distinct error. - Undo on the revert banner now also clears the composer, since it was prefilled by the edit flow and could otherwise be sent as a duplicate. Claude-Session: https://claude.ai/code/session_01T12AhSnQVrSxNnvwfCx2z6 Co-authored-by: engineer <engineer@macbookpro.lan> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -118,7 +118,11 @@
|
||||
"replyFailedTitle": "Reply Failed",
|
||||
"replyFailedMessage": "Could not send your response. Please try again.",
|
||||
"rejectFailedTitle": "Reject Failed",
|
||||
"rejectFailedMessage": "Could not send your response. Please try again."
|
||||
"rejectFailedMessage": "Could not send your response. Please try again.",
|
||||
"imageFailedTitle": "Image not attached",
|
||||
"imageFailedMessage": "One or more images could not be processed. Please try a different photo.",
|
||||
"speechErrorTitle": "Voice input failed",
|
||||
"speechErrorMessage": "Could not use voice input. Check your microphone permission and try again."
|
||||
}
|
||||
},
|
||||
"connection": {
|
||||
|
||||
@@ -118,7 +118,11 @@
|
||||
"replyFailedTitle": "回复失败",
|
||||
"replyFailedMessage": "无法发送您的回复。请重试。",
|
||||
"rejectFailedTitle": "拒绝失败",
|
||||
"rejectFailedMessage": "无法发送您的回复。请重试。"
|
||||
"rejectFailedMessage": "无法发送您的回复。请重试。",
|
||||
"imageFailedTitle": "图片未添加",
|
||||
"imageFailedMessage": "一张或多张图片无法处理。请尝试其他照片。",
|
||||
"speechErrorTitle": "语音输入失败",
|
||||
"speechErrorMessage": "无法使用语音输入。请检查麦克风权限后重试。"
|
||||
}
|
||||
},
|
||||
"connection": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useCallback, useRef } from "react"
|
||||
import { useState, useCallback, useRef, useEffect } from "react"
|
||||
import { ExpoSpeechRecognitionModule, useSpeechRecognitionEvent } from "expo-speech-recognition"
|
||||
|
||||
interface SpeechState {
|
||||
@@ -76,5 +76,13 @@ export function useSpeech(onResult: (text: string) => void): SpeechState & Speec
|
||||
setTranscript("")
|
||||
}, [])
|
||||
|
||||
// Stop the native recognition session when the screen unmounts — otherwise
|
||||
// the mic stays hot in the background. abort() is a no-op when not listening.
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
ExpoSpeechRecognitionModule.abort()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return { listening, transcript, error, start, stop, cancel }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user