import { Output, streamText, tool, type UIMessageStreamWriter } from "ai"; import type { Session } from "next-auth"; import { z } from "zod"; import { getDocumentById, saveSuggestions } from "@/lib/db/queries"; import type { Suggestion } from "@/lib/db/schema"; import type { ChatMessage } from "@/lib/types"; import { generateUUID } from "@/lib/utils"; import { getLanguageModel } from "../providers"; type RequestSuggestionsProps = { session: Session; dataStream: UIMessageStreamWriter; modelId: string; }; export const requestSuggestions = ({ session, dataStream, modelId, }: RequestSuggestionsProps) => tool({ description: "Request writing suggestions for an existing document artifact. Only use this when the user explicitly asks to improve or get suggestions for a document they have already created. Never use for general questions.", inputSchema: z.object({ documentId: z .string() .describe( "The UUID of an existing document artifact that was previously created with createDocument" ), }), execute: async ({ documentId }) => { const document = await getDocumentById({ id: documentId }); if (!document?.content) { return { error: "Document not found", }; } if (document.userId !== session.user?.id) { return { error: "Forbidden" }; } const suggestions: Omit< Suggestion, "userId" | "createdAt" | "documentCreatedAt" >[] = []; const { partialOutputStream } = streamText({ model: getLanguageModel(modelId), system: "You are a writing assistant. Given a piece of writing, offer up to 5 suggestions to improve it. Each suggestion must contain full sentences, not just individual words. Describe what changed and why.", prompt: document.content, output: Output.array({ element: z.object({ originalSentence: z.string().describe("The original sentence"), suggestedSentence: z.string().describe("The suggested sentence"), description: z .string() .describe("The description of the suggestion"), }), }), }); let processedCount = 0; for await (const partialOutput of partialOutputStream) { if (!partialOutput) { continue; } for (let i = processedCount; i < partialOutput.length; i++) { const element = partialOutput[i]; if ( !element?.originalSentence || !element?.suggestedSentence || !element?.description ) { continue; } const suggestion = { originalText: element.originalSentence, suggestedText: element.suggestedSentence, description: element.description, id: generateUUID(), documentId, isResolved: false, }; dataStream.write({ type: "data-suggestion", data: suggestion as Suggestion, transient: true, }); suggestions.push(suggestion); processedCount++; } } if (session.user?.id) { const userId = session.user.id; await saveSuggestions({ suggestions: suggestions.map((suggestion) => ({ ...suggestion, userId, createdAt: new Date(), documentCreatedAt: document.createdAt, })), }); } return { id: documentId, title: document.title, kind: document.kind, message: "Suggestions have been added to the document", }; }, });