feat(web): Add reconnecting feedback when SSE connection is lost (#125)

- Add ReconnectingBanner component to show "Reconnecting..." status
- Track SSE connection state in App.tsx with onDisconnect handler
- Add onBlocked callback to useSendMessage for handling blocked send attempts
- Provide haptic feedback when send is blocked due to no API or session
- Show toast notification when message cannot be sent due to server disconnection
- Add translation keys for reconnecting message and send blocked feedback in en/zh-CN

close #125
This commit is contained in:
weishu
2026-02-01 19:40:43 +08:00
parent 8dfc8749eb
commit 5b27f6b41e
6 changed files with 87 additions and 5 deletions
+31 -4
View File
@@ -18,9 +18,12 @@ type SendMessageInput = {
attachments?: AttachmentMetadata[]
}
type BlockedReason = 'no-api' | 'no-session' | 'pending'
type UseSendMessageOptions = {
resolveSessionId?: (sessionId: string) => Promise<string>
onSessionResolved?: (sessionId: string) => void
onBlocked?: (reason: BlockedReason) => void
}
function findMessageByLocalId(
@@ -88,8 +91,20 @@ export function useSendMessage(
})
const sendMessage = (text: string, attachments?: AttachmentMetadata[]) => {
if (!api || !sessionId) return
if (mutation.isPending || resolveGuardRef.current) return
if (!api) {
options?.onBlocked?.('no-api')
haptic.notification('error')
return
}
if (!sessionId) {
options?.onBlocked?.('no-session')
haptic.notification('error')
return
}
if (mutation.isPending || resolveGuardRef.current) {
options?.onBlocked?.('pending')
return
}
const localId = makeClientSideId('local')
const createdAt = Date.now()
void (async () => {
@@ -123,8 +138,20 @@ export function useSendMessage(
}
const retryMessage = (localId: string) => {
if (!api || !sessionId) return
if (mutation.isPending || resolveGuardRef.current) return
if (!api) {
options?.onBlocked?.('no-api')
haptic.notification('error')
return
}
if (!sessionId) {
options?.onBlocked?.('no-session')
haptic.notification('error')
return
}
if (mutation.isPending || resolveGuardRef.current) {
options?.onBlocked?.('pending')
return
}
const message = findMessageByLocalId(sessionId, localId)
if (!message?.originalText) return