MCPcopy Create free account
hub / github.com/tiann/hapi / useSendMessage

Function useSendMessage

web/src/hooks/mutations/useSendMessage.ts:135–292  ·  view source on GitHub ↗
(
    api: ApiClient | null,
    sessionId: string | null,
    options?: UseSendMessageOptions
)

Source from the content-addressed store, hash-verified

133}
134
135export function useSendMessage(
136 api: ApiClient | null,
137 sessionId: string | null,
138 options?: UseSendMessageOptions
139): {
140 // Resolves true when a mutation was actually started, false when the call was
141 // rejected pre-mutation (no-api / no-session / pending) OR the async
142 // resolveSessionId step threw. Async is required because inactive-session
143 // resume happens before mutation.mutate(), and a sync `true` would let the
144 // caller clear UI state (e.g. pendingSchedule) before knowing whether
145 // resume succeeded — see SessionChat.handleSend.
146 sendMessage: (text: string, attachments?: AttachmentMetadata[], scheduledAt?: number | null) => Promise<boolean>
147 retryMessage: (localId: string) => boolean
148 isSending: boolean
149} {
150 const { haptic } = usePlatform()
151 const [isResolving, setIsResolving] = useState(false)
152 const resolveGuardRef = useRef(false)
153 const isSessionThinkingRef = useRef(options?.isSessionThinking ?? false)
154 isSessionThinkingRef.current = options?.isSessionThinking ?? false
155
156 const mutation = useMutation({
157 mutationFn: async (input: SendMessageInput) => {
158 if (!api) {
159 throw new Error('API unavailable')
160 }
161 await api.sendMessage(input.sessionId, input.text, input.localId, input.attachments, input.scheduledAt)
162 },
163 onMutate: async (input) => {
164 const status = isSessionThinkingRef.current ? 'queued' as const : 'sending' as const
165 appendOptimisticMessage(input.sessionId, createOptimisticMessage(input, status))
166 return { status }
167 },
168 onSuccess: (_, input, context) => {
169 updateMessageStatus(
170 input.sessionId,
171 input.localId,
172 context?.status === 'queued' ? 'queued' : 'sent'
173 )
174 haptic.notification('success')
175 options?.onSuccess?.(input.sessionId)
176 },
177 onError: (error, input) => {
178 // Attachment sends keep the legacy failed-bubble UX: the
179 // composer-restore path can only re-seat text + scheduledAt,
180 // not the uploaded attachment metadata. Removing the row
181 // would destroy the attachment preview AND leave the operator
182 // with no retry surface for it. Keep the row as `failed` so
183 // the in-thread retry button can re-fire the send (with
184 // attachments) via retryMessage.
185 if (input.attachments && input.attachments.length > 0) {
186 updateMessageStatus(input.sessionId, input.localId, 'failed')
187 haptic.notification('error')
188 return
189 }
190 // Text-only sends use the composer-restore path: drop the
191 // optimistic row from the thread (otherwise the failed bubble
192 // would visually duplicate the same text the composer is

Callers 2

SessionPageFunction · 0.90

Calls 6

usePlatformFunction · 0.90
appendOptimisticMessageFunction · 0.90
updateMessageStatusFunction · 0.90
removeOptimisticMessageFunction · 0.90
createOptimisticMessageFunction · 0.85
sendMessageMethod · 0.45

Tested by

no test coverage detected