Okay, we have our dinky little suggestion here and we can ask
"What is the capital of France?"
We see it streaming in and then we see a suggestion streamed in. If we look in the network tab here under API chat and dive into the event stream, if I make this even bigger, we should be able to see that we have our text part being streamed in first, and then we have a data-suggestion part being streamed in afterwards.
The first one starts with
"what"
and then it carries on with
"what is the highest mountain in the world"
Looking at the code here, we can see our MyMessage type is simply a suggestion with a string.
export type MyMessage = UIMessage<never,{suggestion: string}>
This corresponds to a type of data-suggestion and the data being a single string. Further down, we can see that it's being passed into createUIMessageStream, which means that the writer.write method all the way below is type safe.
const stream = createUIMessageStream<MyMessage>({execute: async ({ writer }) => {// ... streaming logic ...writer.write({id: dataPartId,type: "data-suggestion",data: fullSuggestion,})},
We can see that we get autocomplete if we try data-suggestion. Beautiful. We are constructing the full suggestion by appending each chunk to it and then rewriting the same data part to the frontend so that it updates in place.
let fullSuggestion = ""for await (const chunk of followupSuggestionsResult.textStream) {fullSuggestion += chunkwriter.write({id: dataPartId,type: "data-suggestion",data: fullSuggestion,})}
In the front end then when we go to client/root.tsx, we have a little bit of logic for getting the latest suggestion.
const latestSuggestion = messages[messages.length - 1]?.parts.find((part) => part.type === "data-suggestion")?.data
We grab the final message, we find a data part with the type of data-suggestion, and then we grab its data property, meaning that latestSuggestion is either string if the data-suggestion part exists, or it's undefined if there are no messages or if it couldn't find a data part.
And just like that, we're able to stream custom data anytime we like inside a UIMessageStream and use it on the front end. It's an incredibly nice pattern and it enables so many cool things.
Nice work and I will see you in the next one.