UNPKG

@ai-sdk/react

Version:

[React](https://react.dev/) UI components for the [AI SDK](https://ai-sdk.dev/docs):

174 lines (154 loc) 4.89 kB
import { AbstractChat, ChatInit, type CreateUIMessage, type UIMessage, } from 'ai'; import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react'; import { Chat } from './chat.react'; export type { CreateUIMessage, UIMessage }; export type UseChatHelpers<UI_MESSAGE extends UIMessage> = { /** * The id of the chat. */ readonly id: string; /** * Update the `messages` state locally. This is useful when you want to * edit the messages on the client, and then trigger the `reload` method * manually to regenerate the AI response. */ setMessages: ( messages: UI_MESSAGE[] | ((messages: UI_MESSAGE[]) => UI_MESSAGE[]), ) => void; error: Error | undefined; } & Pick< AbstractChat<UI_MESSAGE>, | 'sendMessage' | 'regenerate' | 'stop' | 'resumeStream' | 'addToolResult' | 'addToolOutput' | 'addToolApprovalResponse' | 'status' | 'messages' | 'clearError' >; export type UseChatOptions<UI_MESSAGE extends UIMessage> = ( | { chat: Chat<UI_MESSAGE> } | ChatInit<UI_MESSAGE> ) & { /** * Custom throttle wait in ms for the chat messages and data updates. * Default is undefined, which disables throttling. */ experimental_throttle?: number; /** * Whether to resume an ongoing chat generation stream. */ resume?: boolean; }; export function useChat<UI_MESSAGE extends UIMessage = UIMessage>({ experimental_throttle: throttleWaitMs, resume = false, ...options }: UseChatOptions<UI_MESSAGE> = {}): UseChatHelpers<UI_MESSAGE> { // Create a single ref for all callbacks to avoid stale closures const callbacksRef = useRef( !('chat' in options) ? { onToolCall: options.onToolCall, onData: options.onData, onFinish: options.onFinish, onError: options.onError, sendAutomaticallyWhen: options.sendAutomaticallyWhen, } : {}, ); // Update callbacks ref on each render to keep them current if (!('chat' in options)) { callbacksRef.current = { onToolCall: options.onToolCall, onData: options.onData, onFinish: options.onFinish, onError: options.onError, sendAutomaticallyWhen: options.sendAutomaticallyWhen, }; } // Ensure the Chat instance has the latest callbacks const optionsWithCallbacks: typeof options = { ...options, onToolCall: arg => callbacksRef.current.onToolCall?.(arg), onData: arg => callbacksRef.current.onData?.(arg), onFinish: arg => callbacksRef.current.onFinish?.(arg), onError: arg => callbacksRef.current.onError?.(arg), sendAutomaticallyWhen: arg => callbacksRef.current.sendAutomaticallyWhen?.(arg) ?? false, }; const chatRef = useRef<Chat<UI_MESSAGE>>( 'chat' in options ? options.chat : new Chat(optionsWithCallbacks), ); const shouldRecreateChat = ('chat' in options && options.chat !== chatRef.current) || ('id' in options && chatRef.current.id !== options.id); if (shouldRecreateChat) { chatRef.current = 'chat' in options ? options.chat : new Chat(optionsWithCallbacks); } const subscribeToMessages = useCallback( (update: () => void) => chatRef.current['~registerMessagesCallback'](update, throttleWaitMs), // `chatRef.current.id` is required to trigger re-subscription when the chat ID changes // eslint-disable-next-line react-hooks/exhaustive-deps [throttleWaitMs, chatRef.current.id], ); const messages = useSyncExternalStore( subscribeToMessages, () => chatRef.current.messages, () => chatRef.current.messages, ); const status = useSyncExternalStore( chatRef.current['~registerStatusCallback'], () => chatRef.current.status, () => chatRef.current.status, ); const error = useSyncExternalStore( chatRef.current['~registerErrorCallback'], () => chatRef.current.error, () => chatRef.current.error, ); const setMessages = useCallback( ( messagesParam: UI_MESSAGE[] | ((messages: UI_MESSAGE[]) => UI_MESSAGE[]), ) => { if (typeof messagesParam === 'function') { messagesParam = messagesParam(chatRef.current.messages); } chatRef.current.messages = messagesParam; }, [chatRef], ); useEffect(() => { if (resume) { chatRef.current.resumeStream(); } }, [resume, chatRef]); return { id: chatRef.current.id, messages, setMessages, sendMessage: chatRef.current.sendMessage, regenerate: chatRef.current.regenerate, clearError: chatRef.current.clearError, stop: chatRef.current.stop, error, resumeStream: chatRef.current.resumeStream, status, /** * @deprecated Use `addToolOutput` instead. */ addToolResult: chatRef.current.addToolOutput, addToolOutput: chatRef.current.addToolOutput, addToolApprovalResponse: chatRef.current.addToolApprovalResponse, }; }