UNPKG

@convex-dev/agent

Version:

A agent component for Convex.

93 lines 4.04 kB
"use client"; import { useMemo, useState, useEffect } from "react"; import {} from "ai"; import {} from "../UIMessages.js"; import { blankUIMessage, getParts, updateFromUIMessageChunks, deriveUIMessagesFromTextStreamParts, } from "../deltas.js"; import { useDeltaStreams } from "./useDeltaStreams.js"; // Polyfill structuredClone to support readUIMessageStream on ReactNative if (!("structuredClone" in globalThis)) { // eslint-disable-next-line @typescript-eslint/no-explicit-any void import("@ungap/structured-clone").then(({ default: structuredClone }) => (globalThis.structuredClone = structuredClone)); } /** * A hook that fetches streaming messages from a thread and converts them to UIMessages * using AI SDK's readUIMessageStream. * This ONLY returns streaming UIMessages. To get both full and streaming messages, * use `useUIMessages`. * * @param query The query to use to fetch messages. * It must take as arguments `{ threadId, paginationOpts, streamArgs }` and * return a `streams` object returned from `agent.syncStreams`. * @param args The arguments to pass to the query other than `paginationOpts` * and `streamArgs`. So `{ threadId }` at minimum, plus any other arguments that * you want to pass to the query. * @returns The streaming UIMessages. */ export function useStreamingUIMessages(query, args, options) { const [messageState, setMessageState] = useState({}); const streams = useDeltaStreams(query, args, options); const threadId = args === "skip" ? undefined : args.threadId; useEffect(() => { if (!streams) return; // return if there are no new deltas beyond the cursors let noNewDeltas = true; for (const stream of streams) { const lastDelta = stream.deltas.at(-1); const cursor = messageState[stream.streamMessage.streamId]?.cursor; if (!cursor) { noNewDeltas = false; break; } if (lastDelta && lastDelta.start >= cursor) { noNewDeltas = false; break; } } if (noNewDeltas) { return; } const abortController = new AbortController(); void (async () => { const newMessageState = Object.fromEntries(await Promise.all(streams.map(async ({ deltas, streamMessage }) => { const { parts, cursor } = getParts(deltas, 0); if (streamMessage.format === "UIMessageChunk") { // Unfortunately this can't handle resuming from a UIMessage and // adding more chunks, so we re-create it from scratch each time. const uiMessage = await updateFromUIMessageChunks(blankUIMessage(streamMessage, threadId), parts); return [ streamMessage.streamId, { uiMessage, cursor, }, ]; } else { const [uiMessages] = deriveUIMessagesFromTextStreamParts(threadId, [streamMessage], [], deltas); return [ streamMessage.streamId, { uiMessage: uiMessages[0], cursor, }, ]; } }))); if (abortController.signal.aborted) return; setMessageState(newMessageState); })(); return () => { abortController.abort(); }; }, [messageState, streams, threadId]); return useMemo(() => { if (!streams) return undefined; return streams .map(({ streamMessage }) => messageState[streamMessage.streamId]?.uiMessage) .filter((uiMessage) => uiMessage !== undefined); }, [messageState, streams]); } //# sourceMappingURL=useStreamingUIMessages.js.map