UNPKG

@convex-dev/agent

Version:

A agent component for Convex.

103 lines (100 loc) 3.07 kB
import { insertAtTop } from "convex/react"; import type { MessageDoc, StreamArgs } from "../validators.js"; import type { OptimisticLocalStore } from "convex/browser"; import type { UIMessage } from "../UIMessages.js"; import type { FunctionReference, PaginationOptions, PaginationResult, } from "convex/server"; import type { SyncStreamsReturnValue } from "@convex-dev/agent"; /** * Adds a sent message to the end of a list of messages, so it shows up until * the message is saved on the server and arrives in the query. * It generates a message with fields that match both MessageDoc and UIMessage, * for convenience. It will not include any other fields you might have in your * regular query, however. * * @param query The query used to fetch messages, typically with * useThreadMessages or useUIMessages. * @returns A function that can be used to optimistically send a message. * If your mutation takes different arguments than { threadId, prompt }, you can * use it as a helper function in your optimistic update: * ```ts * const sendMessage = useMutation( * api.chatStreaming.streamStoryAsynchronously, * ).withOptimisticUpdate( * (store, args) => { * optimisticallySendMessage(api.chatStreaming.listThreadMessages)(store, { * threadId: * prompt: whatever you would have passed to the mutation, * }) * } * ); * ``` */ export function optimisticallySendMessage( query: FunctionReference< "query", "public", { threadId: string; paginationOpts: PaginationOptions; streamArgs?: StreamArgs; }, PaginationResult<MessageDoc | UIMessage> & { streams?: SyncStreamsReturnValue; } >, ): ( store: OptimisticLocalStore, args: { threadId: string; prompt: string }, ) => void { return (store, args) => { const queries = store.getAllQueries(query); let maxOrder = -1; for (const q of queries) { if (q.args?.threadId !== args.threadId) continue; if (q.args.streamArgs) continue; for (const m of q.value?.page ?? []) { maxOrder = Math.max(maxOrder, m.order); } } const order = maxOrder + 1; const stepOrder = 0; const id = randomUUID(); const { prompt, ...rest } = args; insertAtTop({ paginatedQuery: query, argsToMatch: { threadId: args.threadId, streamArgs: undefined }, item: { ...rest, _creationTime: Date.now(), _id: id, id, key: `${args.threadId}-${order}-${stepOrder}`, order, stepOrder, status: "pending", tool: false, message: { role: "user", content: prompt }, parts: [{ type: "text", text: prompt }], role: "user", text: prompt, }, localQueryStore: store, }); }; } export function randomUUID() { if ( typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ) { return crypto.randomUUID(); } return ( Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) ); }