feat(store,ui): handle Hub error messages and display error banner
- Handle `action: "error"` messages in connection-store (e.g. UNAUTHORIZED)
- Widen lastError type to `{code, message}` to support all error codes
- Display dismissible error banner in Chat with role="alert" and aria-live
- Add accessible close button with focus-visible ring
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
1f7951df1b
commit
e4f1d51453
2 changed files with 27 additions and 3 deletions
|
|
@ -20,7 +20,6 @@ import {
|
|||
GatewayClient,
|
||||
StreamAction,
|
||||
type ConnectionState,
|
||||
type SendErrorResponse,
|
||||
type StreamPayload,
|
||||
type AgentEvent,
|
||||
type GetAgentMessagesResult,
|
||||
|
|
@ -35,7 +34,7 @@ interface ConnectionStoreState {
|
|||
hubId: string | null
|
||||
agentId: string | null
|
||||
connectionState: ConnectionState
|
||||
lastError: SendErrorResponse | null
|
||||
lastError: { code: string; message: string } | null
|
||||
}
|
||||
|
||||
interface ConnectionStoreActions {
|
||||
|
|
@ -146,13 +145,20 @@ function createClient(
|
|||
return
|
||||
}
|
||||
|
||||
// Handle error messages from Hub (e.g. UNAUTHORIZED)
|
||||
if (msg.action === "error") {
|
||||
const payload = msg.payload as { code: string; message: string }
|
||||
set({ lastError: { code: payload.code, message: payload.message } })
|
||||
return
|
||||
}
|
||||
|
||||
// Handle direct (non-streaming) messages
|
||||
const payload = msg.payload as { agentId?: string; content?: string }
|
||||
if (payload?.agentId && payload?.content) {
|
||||
useMessagesStore.getState().addAssistantMessage(payload.content, payload.agentId)
|
||||
}
|
||||
})
|
||||
.onSendError((error) => set({ lastError: error }))
|
||||
.onSendError((error) => set({ lastError: { code: error.code, message: error.error } }))
|
||||
}
|
||||
|
||||
/** Fetch message history from Hub via RPC after connection is established */
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export function Chat() {
|
|||
const agentId = useConnectionStore((s) => s.agentId)
|
||||
const gwState = useConnectionStore((s) => s.connectionState)
|
||||
const hubId = useConnectionStore((s) => s.hubId)
|
||||
const lastError = useConnectionStore((s) => s.lastError)
|
||||
|
||||
const messages = useMessagesStore((s) => s.messages)
|
||||
const streamingIds = useMessagesStore((s) => s.streamingIds)
|
||||
|
|
@ -65,6 +66,23 @@ export function Chat() {
|
|||
)}
|
||||
</main>
|
||||
|
||||
{/* Error banner */}
|
||||
{lastError && (
|
||||
<div className="px-4 py-2 max-w-4xl mx-auto w-full" role="alert" aria-live="polite">
|
||||
<div className="rounded-md bg-destructive/10 text-destructive text-sm px-3 py-2 flex items-center justify-between">
|
||||
<span>{lastError.message} ({lastError.code})</span>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Dismiss error"
|
||||
onClick={() => useConnectionStore.setState({ lastError: null })}
|
||||
className="text-destructive/60 hover:text-destructive ml-2 text-xs focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 rounded outline-none"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="w-full p-2 pt-1 max-w-4xl mx-auto">
|
||||
<ChatInput
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue