Okay, let's give this a go. We can see the first draft streaming in, then the feedback streams in, and then we get the final text.
Note how we saw the first token immediately, and we can see the full history of the interaction here. Inside our code, we are manually writing the start part. So we're starting the message so we don't get any weird duplicate message issues.
We are then streaming in the writeSlackResult here by monitoring the textStream and then grabbing out a data-slack-message type which we then write to the front end.
We do the same thing further down with this evaluateSlackResult with the textStream again now writing it as the feedback.
And then we get the final slack message down here which again we just merge in and we don't send a start part because we've already sent one.
This is working because we have a type up the top here of MyMessage which has slack-message where the data part is a string and the slack-message-feedback where the data value is a string.
This is then being used in a bunch of different places including in components where we use it to type the MyMessage['parts'] here.
We then map over each part and render it to the front end.
And because the parts are kept in the same order that they were streamed down, it means that we get a really nice view of everything that's happened so far in the stream.
So hopefully this setup gives you a sense for how you can stream down multiple parts of a workflow as they happen. Putting them in their own custom data part means you get a lot of flexibility in the front end with how you want to render them. So I find that even though this is a lot of code here, where you have to manually convert from this textStream into a custom data part, you do end up getting a lot out of it.
Nice work, and I will see you in the next one.
writer.write({type: "start",})
for await (const part of writeSlackResult.textStream) {firstDraft += partwriter.write({type: "data-slack-message",data: firstDraft,id: firstDraftId,})}
for await (const part of evaluateSlackResult.textStream) {feedback += partwriter.write({type: "data-slack-message-feedback",data: feedback,id: feedbackId,})}
writer.merge(finalSlackAttempt.toUIMessageStream({sendStart: false,}))
export type MyMessage = UIMessage<unknown,{"slack-message": string"slack-message-feedback": string}>
export const Message = ({role,parts,}: {role: stringparts: MyMessage["parts"]}) => (<div className="my-4">{parts.map((part) => {if (part.type === "data-slack-message") {return (<div key={part.id} className="mb-4"><h2 className="text-gray-300 text-sm mb-1">First draft</h2><p className="text-gray-400 text-xs">{part.data}</p></div>)}if (part.type === "data-slack-message-feedback") {return (<div key={part.id} className="mb-4"><h2 className="text-gray-300 text-sm mb-1">Feedback</h2><p className="text-gray-400 text-xs">{part.data}</p></div>)}// ...})}</div>)