@@ -8,8 +8,10 @@ import {
88} from '@angular/core' ;
99import { FormBuilder , ReactiveFormsModule , Validators } from '@angular/forms' ;
1010import {
11+ type AgUiChatMessage ,
1112 agUiResource ,
1213 type AgUiToolCall ,
14+ type AgUiWidgetInstance ,
1315 type AgUiWorkflowStep ,
1416 createShowComponentsTool ,
1517 WidgetContainerComponent ,
@@ -70,40 +72,31 @@ export class TravelPlannerPage {
7072 } ) ;
7173
7274 protected readonly widgets = computed ( ( ) =>
73- this . chat
74- . value ( )
75- . filter ( ( message ) => message . role === 'assistant' )
76- . flatMap ( ( message ) => message . widgets ) ,
75+ selectAssistantWidgets ( this . chat . value ( ) ) ,
7776 ) ;
7877
7978 protected readonly messageWidgets = computed ( ( ) =>
80- this . widgets ( ) . filter ( ( widget ) => widget . name === 'messageWidget' ) ,
79+ selectWidgetsByName ( this . widgets ( ) , 'messageWidget' ) ,
8180 ) ;
8281
8382 protected readonly flightWidgets = computed ( ( ) =>
84- this . widgets ( ) . filter ( ( widget ) => widget . name === 'flightWidget' ) ,
83+ selectWidgetsByName ( this . widgets ( ) , 'flightWidget' ) ,
8584 ) ;
8685
8786 protected readonly hotelWidgets = computed ( ( ) =>
88- this . widgets ( ) . filter ( ( widget ) => widget . name === 'hotelWidget' ) ,
87+ selectWidgetsByName ( this . widgets ( ) , 'hotelWidget' ) ,
8988 ) ;
9089
91- protected readonly otherWidgets = computed ( ( ) => {
92- const known = new Set ( [ 'messageWidget' , 'flightWidget' , 'hotelWidget' ] ) ;
93- return this . widgets ( ) . filter ( ( widget ) => ! known . has ( widget . name ) ) ;
94- } ) ;
90+ protected readonly otherWidgets = computed ( ( ) =>
91+ selectOtherWidgets ( this . widgets ( ) ) ,
92+ ) ;
9593
96- protected readonly errorMessage = computed < string | null > ( ( ) => {
97- const error = this . chat . value ( ) . find ( ( message ) => message . role === 'error' ) ;
98- return error ?. content ?? null ;
99- } ) ;
94+ protected readonly errorMessage = computed < string | null > ( ( ) =>
95+ readErrorMessage ( this . chat . value ( ) ) ,
96+ ) ;
10097
10198 protected readonly toolCalls = computed < AgUiToolCall [ ] > ( ( ) =>
102- this . chat
103- . value ( )
104- . filter ( ( message ) => message . role === 'assistant' )
105- . flatMap ( ( message ) => message . toolCalls )
106- . filter ( ( toolCall ) => toolCall . name !== 'showComponents' ) ,
99+ selectVisibleToolCalls ( this . chat . value ( ) ) ,
107100 ) ;
108101
109102 /**
@@ -112,61 +105,27 @@ export class TravelPlannerPage {
112105 * small extra section below the step timeline so they aren't lost.
113106 */
114107 protected readonly topLevelToolCalls = computed < AgUiToolCall [ ] > ( ( ) =>
115- this . toolCalls ( ) . filter (
116- ( toolCall ) =>
117- ! toolCall . stepName && ! toolCall . name . startsWith ( 'workflow-' ) ,
118- ) ,
108+ selectTopLevelToolCalls ( this . toolCalls ( ) ) ,
119109 ) ;
120110
121111 protected readonly workflowSteps = computed < AgUiWorkflowStep [ ] > ( ( ) =>
122- this . chat
123- . value ( )
124- . filter ( ( message ) => message . role === 'assistant' )
125- . flatMap ( ( message ) => message . workflowSteps ) ,
112+ selectWorkflowSteps ( this . chat . value ( ) ) ,
126113 ) ;
127114
128115 protected readonly toolCallsByStep = computed < Map < string , AgUiToolCall [ ] > > (
129- ( ) => {
130- const map = new Map < string , AgUiToolCall [ ] > ( ) ;
131- for ( const toolCall of this . toolCalls ( ) ) {
132- const key = toolCall . stepName ;
133- if ( ! key ) {
134- continue ;
135- }
136- const list = map . get ( key ) ;
137- if ( list ) {
138- list . push ( toolCall ) ;
139- } else {
140- map . set ( key , [ toolCall ] ) ;
141- }
142- }
143- return map ;
144- } ,
116+ ( ) => groupToolCallsByStep ( this . toolCalls ( ) ) ,
145117 ) ;
146118
147119 protected readonly currentWorkflowStep = computed < string | null > ( ( ) => {
148- const steps = this . workflowSteps ( ) ;
149- for ( let i = steps . length - 1 ; i >= 0 ; i -= 1 ) {
150- const step = steps [ i ] ;
151- if ( step . status === 'pending' ) {
152- return WORKFLOW_STEP_LABELS [ step . name ] ?? step . name ;
153- }
154- }
155- return null ;
120+ return readCurrentWorkflowStep ( this . workflowSteps ( ) , WORKFLOW_STEP_LABELS ) ;
156121 } ) ;
157122
158123 protected readonly currentStatus = computed < string > ( ( ) => {
159- const step = this . currentWorkflowStep ( ) ;
160- if ( step ) {
161- return step ;
162- }
163- if ( this . chat . isLoading ( ) ) {
164- return 'Building travel plan' ;
165- }
166- if ( this . widgets ( ) . length > 0 ) {
167- return 'Done' ;
168- }
169- return 'Ready' ;
124+ return readCurrentStatus (
125+ this . currentWorkflowStep ( ) ,
126+ this . chat . isLoading ( ) ,
127+ this . widgets ( ) . length ,
128+ ) ;
170129 } ) ;
171130
172131 protected readonly showToolDetails = signal ( false ) ;
@@ -211,21 +170,11 @@ export class TravelPlannerPage {
211170 }
212171
213172 protected formatToolArgs ( args : unknown ) : string {
214- if ( args === undefined || args === null ) {
215- return '' ;
216- }
217- if ( typeof args === 'string' ) {
218- return args ;
219- }
220- try {
221- return JSON . stringify ( args , null , 2 ) ;
222- } catch {
223- return String ( args ) ;
224- }
173+ return formatToolArgsValue ( args ) ;
225174 }
226175
227176 protected stepLabel ( name : string ) : string {
228- return WORKFLOW_STEP_LABELS [ name ] ?? name ;
177+ return readStepLabel ( name , WORKFLOW_STEP_LABELS ) ;
229178 }
230179
231180 protected hasWorkflowSteps ( ) : boolean {
@@ -236,3 +185,116 @@ export class TravelPlannerPage {
236185 return this . toolCallsByStep ( ) . get ( stepName ) ?? [ ] ;
237186 }
238187}
188+
189+ function selectAssistantWidgets (
190+ messages : AgUiChatMessage [ ] ,
191+ ) : AgUiWidgetInstance [ ] {
192+ return messages
193+ . filter ( ( message ) => message . role === 'assistant' )
194+ . flatMap ( ( message ) => message . widgets ) ;
195+ }
196+
197+ function selectWidgetsByName (
198+ widgets : AgUiWidgetInstance [ ] ,
199+ name : string ,
200+ ) : AgUiWidgetInstance [ ] {
201+ return widgets . filter ( ( widget ) => widget . name === name ) ;
202+ }
203+
204+ function selectOtherWidgets (
205+ widgets : AgUiWidgetInstance [ ] ,
206+ ) : AgUiWidgetInstance [ ] {
207+ const known = new Set ( [ 'messageWidget' , 'flightWidget' , 'hotelWidget' ] ) ;
208+ return widgets . filter ( ( widget ) => ! known . has ( widget . name ) ) ;
209+ }
210+
211+ function readErrorMessage ( messages : AgUiChatMessage [ ] ) : string | null {
212+ const error = messages . find ( ( message ) => message . role === 'error' ) ;
213+ return error ?. content ?? null ;
214+ }
215+
216+ function selectVisibleToolCalls ( messages : AgUiChatMessage [ ] ) : AgUiToolCall [ ] {
217+ return messages
218+ . filter ( ( message ) => message . role === 'assistant' )
219+ . flatMap ( ( message ) => message . toolCalls )
220+ . filter ( ( toolCall ) => toolCall . name !== 'showComponents' ) ;
221+ }
222+
223+ function selectTopLevelToolCalls ( toolCalls : AgUiToolCall [ ] ) : AgUiToolCall [ ] {
224+ return toolCalls . filter (
225+ ( toolCall ) => ! toolCall . stepName && ! toolCall . name . startsWith ( 'workflow-' ) ,
226+ ) ;
227+ }
228+
229+ function selectWorkflowSteps ( messages : AgUiChatMessage [ ] ) : AgUiWorkflowStep [ ] {
230+ return messages
231+ . filter ( ( message ) => message . role === 'assistant' )
232+ . flatMap ( ( message ) => message . workflowSteps ) ;
233+ }
234+
235+ function groupToolCallsByStep (
236+ toolCalls : AgUiToolCall [ ] ,
237+ ) : Map < string , AgUiToolCall [ ] > {
238+ const map = new Map < string , AgUiToolCall [ ] > ( ) ;
239+ for ( const toolCall of toolCalls ) {
240+ const key = toolCall . stepName ;
241+ if ( ! key ) {
242+ continue ;
243+ }
244+ const list = map . get ( key ) ;
245+ if ( list ) {
246+ list . push ( toolCall ) ;
247+ } else {
248+ map . set ( key , [ toolCall ] ) ;
249+ }
250+ }
251+ return map ;
252+ }
253+
254+ function readCurrentWorkflowStep (
255+ steps : AgUiWorkflowStep [ ] ,
256+ labels : Record < string , string > ,
257+ ) : string | null {
258+ for ( let i = steps . length - 1 ; i >= 0 ; i -= 1 ) {
259+ const step = steps [ i ] ;
260+ if ( step . status === 'pending' ) {
261+ return labels [ step . name ] ?? step . name ;
262+ }
263+ }
264+ return null ;
265+ }
266+
267+ function readCurrentStatus (
268+ currentWorkflowStep : string | null ,
269+ isLoading : boolean ,
270+ widgetCount : number ,
271+ ) : string {
272+ if ( currentWorkflowStep ) {
273+ return currentWorkflowStep ;
274+ }
275+ if ( isLoading ) {
276+ return 'Building travel plan' ;
277+ }
278+ if ( widgetCount > 0 ) {
279+ return 'Done' ;
280+ }
281+ return 'Ready' ;
282+ }
283+
284+ function formatToolArgsValue ( args : unknown ) : string {
285+ if ( args === undefined || args === null ) {
286+ return '' ;
287+ }
288+ if ( typeof args === 'string' ) {
289+ return args ;
290+ }
291+ try {
292+ return JSON . stringify ( args , null , 2 ) ;
293+ } catch {
294+ return String ( args ) ;
295+ }
296+ }
297+
298+ function readStepLabel ( name : string , labels : Record < string , string > ) : string {
299+ return labels [ name ] ?? name ;
300+ }
0 commit comments