@@ -5,6 +5,125 @@ import type { UIMessage, UIMessageChunk } from "ai";
55import { Streamdown } from "streamdown" ;
66
77export function AIChat ( { accessToken, runId } : { accessToken : string ; runId : string } ) {
8+ return (
9+ < div className = "space-y-8" >
10+ < AIChatStats accessToken = { accessToken } runId = { runId } />
11+ < AIChatFull accessToken = { accessToken } runId = { runId } />
12+ </ div >
13+ ) ;
14+ }
15+
16+ function AIChatStats ( { accessToken, runId } : { accessToken : string ; runId : string } ) {
17+ const { parts, error } = useRealtimeStream < UIMessageChunk > ( runId , "chat" , {
18+ accessToken,
19+ baseURL : process . env . NEXT_PUBLIC_TRIGGER_API_URL ,
20+ timeoutInSeconds : 600 ,
21+ } ) ;
22+
23+ if ( error ) return < div className = "text-red-600 font-semibold" > Error: { error . message } </ div > ;
24+
25+ if ( ! parts || parts . length === 0 ) {
26+ return (
27+ < div className = "p-4 rounded-lg bg-blue-50 border-l-4 border-blue-500" >
28+ < div className = "text-sm font-semibold text-blue-900 mb-2" > 📊 Stream Statistics</ div >
29+ < div className = "text-xs text-blue-700" > Waiting for stream data...</ div >
30+ </ div >
31+ ) ;
32+ }
33+
34+ // Calculate statistics
35+ const stats = {
36+ totalChunks : parts . length ,
37+ textStartChunks : 0 ,
38+ textDeltaChunks : 0 ,
39+ textEndChunks : 0 ,
40+ errorChunks : 0 ,
41+ otherChunks : 0 ,
42+ totalCharacters : 0 ,
43+ averageChunkSize : 0 ,
44+ } ;
45+
46+ const chunkTimings : number [ ] = [ ] ;
47+ let firstChunkTime : number | null = null ;
48+ let lastChunkTime : number | null = null ;
49+
50+ for ( const chunk of parts ) {
51+ switch ( chunk . type ) {
52+ case "text-start" :
53+ stats . textStartChunks ++ ;
54+ break ;
55+ case "text-delta" :
56+ stats . textDeltaChunks ++ ;
57+ stats . totalCharacters += chunk . delta . length ;
58+ break ;
59+ case "text-end" :
60+ stats . textEndChunks ++ ;
61+ break ;
62+ case "error" :
63+ stats . errorChunks ++ ;
64+ break ;
65+ default :
66+ stats . otherChunks ++ ;
67+ }
68+ }
69+
70+ stats . averageChunkSize =
71+ stats . textDeltaChunks > 0 ? Math . round ( stats . totalCharacters / stats . textDeltaChunks ) : 0 ;
72+
73+ const isStreaming = stats . textEndChunks === 0 ;
74+
75+ return (
76+ < div className = "p-4 rounded-lg bg-blue-50 border-l-4 border-blue-500" >
77+ < div className = "text-sm font-semibold text-blue-900 mb-3" >
78+ 📊 Stream Statistics { isStreaming && < span className = "text-blue-600" > (live)</ span > }
79+ </ div >
80+
81+ < div className = "grid grid-cols-2 md:grid-cols-4 gap-4 text-xs" >
82+ < div >
83+ < div className = "text-blue-600 font-semibold" > Total Chunks</ div >
84+ < div className = "text-blue-900 text-lg font-bold" > { stats . totalChunks } </ div >
85+ </ div >
86+ < div >
87+ < div className = "text-blue-600 font-semibold" > Text Deltas</ div >
88+ < div className = "text-blue-900 text-lg font-bold" > { stats . textDeltaChunks } </ div >
89+ </ div >
90+ < div >
91+ < div className = "text-blue-600 font-semibold" > Total Characters</ div >
92+ < div className = "text-blue-900 text-lg font-bold" > { stats . totalCharacters } </ div >
93+ </ div >
94+ < div >
95+ < div className = "text-blue-600 font-semibold" > Avg Chunk Size</ div >
96+ < div className = "text-blue-900 text-lg font-bold" > { stats . averageChunkSize } chars</ div >
97+ </ div >
98+ </ div >
99+
100+ < div className = "mt-3 pt-3 border-t border-blue-200 grid grid-cols-2 md:grid-cols-5 gap-2 text-xs" >
101+ < div >
102+ < span className = "text-blue-600" > text-start:</ span > { " " }
103+ < span className = "text-blue-900 font-semibold" > { stats . textStartChunks } </ span >
104+ </ div >
105+ < div >
106+ < span className = "text-blue-600" > text-delta:</ span > { " " }
107+ < span className = "text-blue-900 font-semibold" > { stats . textDeltaChunks } </ span >
108+ </ div >
109+ < div >
110+ < span className = "text-blue-600" > text-end:</ span > { " " }
111+ < span className = "text-blue-900 font-semibold" > { stats . textEndChunks } </ span >
112+ </ div >
113+ < div >
114+ < span className = "text-blue-600" > errors:</ span > { " " }
115+ < span className = "text-blue-900 font-semibold" > { stats . errorChunks } </ span >
116+ </ div >
117+ < div >
118+ < span className = "text-blue-600" > other:</ span > { " " }
119+ < span className = "text-blue-900 font-semibold" > { stats . otherChunks } </ span >
120+ </ div >
121+ </ div >
122+ </ div >
123+ ) ;
124+ }
125+
126+ function AIChatFull ( { accessToken, runId } : { accessToken : string ; runId : string } ) {
8127 const { parts, error } = useRealtimeStream < UIMessageChunk > ( runId , "chat" , {
9128 accessToken,
10129 baseURL : process . env . NEXT_PUBLIC_TRIGGER_API_URL ,
0 commit comments