@avearra/hooks
v0.0.2
Published
Comprehensive collection of React hooks for AI, chat, UI, data management, and more
Maintainers
Readme
@avearra/hooks
A headless, provider-agnostic hooks suite for Agentic Chat UIs. Import from @avearra/hooks (or via avearra).
- Categories: chat, ai, voice, data, streaming, media, storage, ui, a11y, state, utils
- Design: Typed, SSR-safe, controlled/uncontrolled, composable.
Quick Start
import { useChat, useMessageComposer, useModel } from '@avearra/hooks';Chat
useChat
Manages transcript, input, and attachments; sends one user message (text + attachments).
API
const chat = useChat({
onSend: async (userMsg) => {/* return assistant message */},
allowAttachmentsOnly: false,
});
// chat: { messages, input, setInput, attachments, addAttachments, removeAttachment, clearAttachments, send, clear, isEmpty }Example
const chat = useChat({ allowAttachmentsOnly: true, onSend: async (u) => ({ id: crypto.randomUUID(), role: 'assistant', content: 'Echo: ' + (u.content || (u.attachments?.length ?? 0) + ' files'), createdAt: Date.now() }) });
<form onSubmit={(e)=>{e.preventDefault(); chat.send();}}>
<input value={chat.input} onChange={(e)=>chat.setInput(e.target.value)} />
<button type="submit">Send</button>
</form>useMessageComposer
Headless input area controller for text + attachments, with validation.
const composer = useMessageComposer({ maxAttachments: 4, accept: ['image/', /pdf$/], maxSize: 5_000_000 });
// { text, setText, attachments, addFiles, removeAttachment, clearAttachments, readyToSend }useChatHistory
Local storage sessions map: get/set/append/clear per session.
const hist = useChatHistory();
hist.append('session-1', ...messages);useChatStreaming
Token streaming state for assistant responses.
const { start, stop, partial, status } = useChatStreaming();AI
useModel
Provider-agnostic model caller; supports streaming via async generators.
const { callModel, running } = useModel(adapter);
const res = await callModel({ messages, stream: true, onToken: (t)=>setText(s=>s+t) });useCompletion
Simple completion on top of useModel.
const { text, complete, running } = useCompletion(adapter);
await complete('Hello');useToolCalls
Run model-requested tools deterministically.
const { run } = useToolCalls();
await run([{ name: 'search', args: { q: 'nx' } }], { search: async ({q})=>[] });useRagQuery
Perform retrieval to augment prompts.
const { results, search } = useRagQuery(async (q)=>[{ id:'1', score:0.9, text:'...' }]);useSafetyChecks
Compose moderation rules.
const { check } = useSafetyChecks([(p)=> p.content.includes('bad') ? { ok:false, reason:'policy' } : { ok:true }]);Media and Input
useFileDropzone
const dz = useFileDropzone({ onFiles: addFiles, accept: ['image/'] });
<div {...dz.getRootProps()}>Drop files</div>usePaste
const { onPaste } = usePaste({ onFiles: addFiles, onText: setText });
<div onPaste={onPaste} />Clipboard
const { copy, copied } = useClipboard();Streaming
- useAbortableFetch: fetch + AbortController
- useEventSource: SSE client
- useReadableStream: read browser streams
Data
- useQuery: minimal cached query with staleTime
- useMutation: async mutation state
- usePolling: interval polling
UI, A11y, State
- useDisclosure, useOnClickOutside, useKeyboardShortcut, usePresence, useFocusTrap, useRovingFocus
- useFocusRing, useLiveRegion
- useDebouncedValue, useThrottledValue, useControllableState, useCounter
- usePreferences, useLocalStorage, useSessionStorage
- useId, useDimensions, useIntersection, useResizeObserver, useMediaQuery, useTimeout, useInterval
Recipe: Compose chat input
const chat = useChat({ onSend });
const composer = useMessageComposer({ maxAttachments: 4 });
const dropzone = useFileDropzone({ onFiles: composer.addFiles, accept: ['image/'] });
const paste = usePaste({ onFiles: composer.addFiles, onText: (t)=>chat.setInput(t) });
<form onSubmit={(e)=>{ e.preventDefault(); chat.setInput(composer.text); chat.addAttachments([]); chat.send(); composer.clearAttachments(); }} {...dropzone.getRootProps()} onPaste={paste.onPaste}>
{/* render composer.attachments thumbnails */}
<input value={chat.input} onChange={(e)=>chat.setInput(e.target.value)} />
</form>