All right, let's ask our system how many R's are in the word strawberry. We get flashlight.
There are two R's
and Gemini 2.0 flash.
There are three R's.
We also have two select buttons here.
So let's select the correct answer. And we noticed that it gets promoted here. We can ask it a follow up, which then returns two more data-output parts and let's see if they got it right. Oh my gosh they're both sycophants. They both think they've got it wrong and they both chose two.
Oh dear Gemini 2.0 Flash I had such high hopes for you. But we can see we've integrated an A-B test pretty successfully into our UI. Let's take a look in our API chat.ts file first. We can see that we're awaiting Promise.all and passing the two calls here. We're passing in some model text for gemini-2.0-flash-lite and gemini-2.0-flash, passing in the correct text streams.
await Promise.all([streamModelText({textStream: firstStreamResult.textStream,model: "Gemini 2.0 Flash Lite",writer,}),streamModelText({textStream: secondStreamResult.textStream,model: "Gemini 2.0 Flash",writer,}),
Inside streamModelText up here, we are streaming down the data part. We're concatenating the text of the text stream into fullText and writing those into data-output, crucially passing in the part ID so that it continually updates the same part.
const streamModelText = async (opts: {textStream: AsyncIterableStream<string>model: stringwriter: UIMessageStreamWriter<MyMessage>}) => {const partId = crypto.randomUUID()
And of course, that part is typed inside MyMessage with data-output being a model and text.
export type MyMessage = UIMessage<never,{output: {model: stringtext: string}}>
Let's take a look at the front end specifically in onSelectModel. We are creating a new messages array here by just slicing all of the messages up until the current message index.
onSelectModel={(partId) => {const part = message.parts.filter((part) => part.type === 'data-output').find((part) => part.id === partId);if (!part) {return;
Then we are pushing this new message to that array with all of the attributes of the original message and some text as the only part. Finally, we are setting the messages, which we get from useChat up here to be the content of those new messages.
Hopefully this exercise gives you an insight to the ways that you can manipulate the shape of the messages on the front end as well as giving you ideas for running parallel streams.
Very nice work, not a simple exercise. I will see you in the next one.
const { messages, setMessages } = useChat<MyMessage>({});