@@ -6,9 +6,8 @@ import { type UIMessage } from "../UIMessages.js";
66import {
77 blankUIMessage ,
88 getParts ,
9- statusFromStreamStatus ,
109 updateFromUIMessageChunks ,
11- updateFromTextStreamParts ,
10+ deriveUIMessagesFromTextStreamParts ,
1211} from "../deltas.js" ;
1312import { useDeltaStreams } from "./useDeltaStreams.js" ;
1413
@@ -66,75 +65,68 @@ export function useStreamingUIMessages<
6665
6766 useEffect ( ( ) => {
6867 if ( ! streams ) return ;
68+ // return if there are no new deltas beyond the cursors
69+ let noNewDeltas = true ;
70+ for ( const stream of streams ) {
71+ const lastDelta = stream . deltas . at ( - 1 ) ;
72+ const cursor = messageState [ stream . streamMessage . streamId ] ?. cursor ;
73+ if ( ! cursor ) {
74+ noNewDeltas = false ;
75+ break ;
76+ }
77+ if ( lastDelta && lastDelta . start >= cursor ) {
78+ noNewDeltas = false ;
79+ break ;
80+ }
81+ }
82+ if ( noNewDeltas ) {
83+ return ;
84+ }
6985 const abortController = new AbortController ( ) ;
7086 void ( async ( ) => {
71- let changed = false ;
7287 const newMessageState : Record <
7388 string ,
7489 {
7590 uiMessage : UIMessage < METADATA , DATA_PARTS , TOOLS > ;
7691 cursor : number ;
7792 }
78- > = { } ;
79- for ( const stream of streams ) {
80- if ( abortController . signal . aborted ) return ;
81- const oldState = messageState [ stream . streamMessage . streamId ] ;
82- let uiMessage = oldState ?. uiMessage ;
83- if ( ! oldState ) {
84- changed = true ;
85- uiMessage = blankUIMessage (
86- stream . streamMessage ,
87- threadId ,
88- ) as UIMessage < METADATA , DATA_PARTS , TOOLS > ;
89- }
90- const { parts, cursor } = getParts < UIMessageChunk > (
91- stream . deltas ,
92- oldState ?. cursor ,
93- ) ;
94- if ( parts . length ) {
95- changed = true ;
96- if ( stream . streamMessage . format === "UIMessageChunk" ) {
97- uiMessage = ( await updateFromUIMessageChunks (
98- uiMessage ,
99- parts ,
100- ) ) as UIMessage < METADATA , DATA_PARTS , TOOLS > ;
101- if (
102- uiMessage . status !== "failed" &&
103- uiMessage . status !== "success"
104- ) {
105- uiMessage . status = statusFromStreamStatus (
106- stream . streamMessage . status ,
93+ > = Object . fromEntries (
94+ await Promise . all (
95+ streams . map ( async ( { deltas, streamMessage } ) => {
96+ const { parts, cursor } = getParts < UIMessageChunk > ( deltas , 0 ) ;
97+ if ( streamMessage . format === "UIMessageChunk" ) {
98+ // Unfortunately this can't handle resuming from a UIMessage and
99+ // adding more chunks, so we re-create it from scratch each time.
100+ const uiMessage = await updateFromUIMessageChunks (
101+ blankUIMessage ( streamMessage , threadId ) ,
102+ parts ,
107103 ) ;
104+ return [
105+ streamMessage . streamId ,
106+ {
107+ uiMessage,
108+ cursor,
109+ } ,
110+ ] ;
111+ } else {
112+ const [ uiMessages ] = deriveUIMessagesFromTextStreamParts (
113+ threadId ,
114+ [ streamMessage ] ,
115+ [ ] ,
116+ deltas ,
117+ ) ;
118+ return [
119+ streamMessage . streamId ,
120+ {
121+ uiMessage : uiMessages [ 0 ] ,
122+ cursor,
123+ } ,
124+ ] ;
108125 }
109- } else if (
110- stream . streamMessage . format === "TextStreamPart" ||
111- ! stream . streamMessage . format
112- ) {
113- const updated = updateFromTextStreamParts (
114- threadId ,
115- stream . streamMessage ,
116- {
117- streamId : stream . streamMessage . streamId ,
118- cursor,
119- message : uiMessage ,
120- } ,
121- stream . deltas ,
122- ) [ 0 ] ;
123- uiMessage = updated . message as UIMessage <
124- METADATA ,
125- DATA_PARTS ,
126- TOOLS
127- > ;
128- } else {
129- console . error ( "Unknown format" , stream . streamMessage . format ) ;
130- }
131- }
132- newMessageState [ stream . streamMessage . streamId ] = {
133- uiMessage,
134- cursor,
135- } ;
136- }
137- if ( ! changed || abortController . signal . aborted ) return ;
126+ } ) ,
127+ ) ,
128+ ) ;
129+ if ( abortController . signal . aborted ) return ;
138130 setMessageState ( newMessageState ) ;
139131 } ) ( ) ;
140132 return ( ) => {
0 commit comments