@@ -34,6 +34,7 @@ import { SiAiMessageComponent } from './si-ai-message.component';
3434import { SiChatContainerInputDirective } from './si-chat-container-input.directive' ;
3535import { SiChatContainerComponent } from './si-chat-container.component' ;
3636import { ChatInputAttachment , SiChatInputComponent } from './si-chat-input.component' ;
37+ import { SiToolMessageComponent } from './si-tool-message.component' ;
3738import { SiUserMessageComponent } from './si-user-message.component' ;
3839
3940/** @experimental */
@@ -44,6 +45,7 @@ import { SiUserMessageComponent } from './si-user-message.component';
4445 SiEmptyStateComponent ,
4546 SiInlineNotificationComponent ,
4647 SiAiMessageComponent ,
48+ SiToolMessageComponent ,
4749 SiUserMessageComponent ,
4850 SiChatContainerComponent ,
4951 SiChatContainerInputDirective
@@ -185,6 +187,30 @@ export class SiAiChatContainerComponent {
185187 */
186188 readonly statusAction = input < { title : string ; href : string ; target ?: string } > ( ) ;
187189
190+ /**
191+ * Label for tool message input arguments section
192+ *
193+ * @defaultValue
194+ * ```
195+ * t(() => $localize`:@@SI_TOOL_MESSAGE.INPUT_ARGUMENTS:Input Arguments`)
196+ * ```
197+ */
198+ readonly toolInputArgumentsLabel = input < TranslatableString > (
199+ t ( ( ) => $localize `:@@SI_TOOL_MESSAGE.INPUT_ARGUMENTS:Input Arguments` )
200+ ) ;
201+
202+ /**
203+ * Label for tool message output section
204+ *
205+ * @defaultValue
206+ * ```
207+ * t(() => $localize`:@@SI_TOOL_MESSAGE.OUTPUT:Output`)
208+ * ```
209+ */
210+ readonly toolOutputLabel = input < TranslatableString > (
211+ t ( ( ) => $localize `:@@SI_TOOL_MESSAGE.OUTPUT:Output` )
212+ ) ;
213+
188214 /**
189215 * Emitted when a new message is sent
190216 */
@@ -207,6 +233,7 @@ export class SiAiChatContainerComponent {
207233 const messages = this . messages ( ) ;
208234 if ( ! messages ?. length ) {
209235 if ( this . loading ( ) ) {
236+ // If loading but no messages, show a single loading AI message
210237 const loadingMessage : AiChatMessage = {
211238 type : 'ai' ,
212239 content : '' ,
@@ -217,11 +244,16 @@ export class SiAiChatContainerComponent {
217244 return [ ] ;
218245 }
219246
247+ // If global loading is true, check if we need to add an AI message
220248 if ( this . loading ( ) && messages . length > 0 ) {
221249 const latestMessage = messages [ messages . length - 1 ] ;
222250
251+ // Add empty AI message if:
252+ // 1. Latest message is tool call and global loading is on, OR
253+ // 2. Latest message has content and not loading, but global loading is on
223254 const shouldAddAiMessage =
224255 this . isTemplateMessage ( latestMessage ) ||
256+ latestMessage . type === 'tool' ||
225257 ( latestMessage . type === 'ai' &&
226258 this . getContentValue ( latestMessage . content ) &&
227259 ! this . getLoadingState ( latestMessage . loading , latestMessage . content , false ) &&
@@ -247,7 +279,7 @@ export class SiAiChatContainerComponent {
247279 primary : MessageAction [ ] ;
248280 secondary : MenuItem [ ] ;
249281 } {
250- if ( this . isTemplateMessage ( message ) ) {
282+ if ( this . isTemplateMessage ( message ) || message . type === 'tool' ) {
251283 return { primary : [ ] , secondary : [ ] } ;
252284 }
253285
@@ -297,49 +329,100 @@ export class SiAiChatContainerComponent {
297329 }
298330 }
299331
300- protected getContentValue ( content : string | Signal < string > | undefined ) : string {
301- if ( isSignal ( content ) ) {
302- return content ( ) ;
303- }
304- return content ?? '' ;
332+ protected getContentValue < T extends string | object > ( content : T | Signal < T > | undefined ) : T {
333+ if ( ! content ) return '' as T ;
334+ return isSignal ( content ) ? ( content as Signal < T > ) ( ) : content ;
305335 }
306336
307- protected getOutputValue ( content : string | Signal < string > | undefined ) : string | Signal < string > {
308- return content ?? '' ;
337+ protected getOutputValue (
338+ outputValue : string | object | Signal < string | object > | undefined
339+ ) : string | object | undefined {
340+ if ( outputValue === undefined || outputValue === null ) return undefined ;
341+ return isSignal ( outputValue ) ? ( outputValue as Signal < string | object > ) ( ) : outputValue ;
309342 }
310343
311- private isEmptyContent ( content : string | Signal < string > | undefined ) : boolean {
344+ private isEmptyContent ( content : string | object | Signal < string | object > | undefined ) : boolean {
312345 const contentValue = this . getContentValue ( content ) ;
313- return ! contentValue || contentValue . trim ( ) . length === 0 ;
346+
347+ return ! contentValue ;
314348 }
315349
316350 private getLoadingValue ( loading : boolean | Signal < boolean > | undefined ) : boolean {
317- if ( isSignal ( loading ) ) {
318- return loading ( ) ;
319- }
320- return loading ?? false ;
351+ return loading !== undefined ? ( isSignal ( loading ) ? loading ( ) : loading ) : false ;
321352 }
322353
323- private isStreamingContent ( content : string | Signal < string > | undefined ) : boolean {
324- return isSignal ( content ) && this . getContentValue ( content ) . trim ( ) . length > 0 ;
354+ private isStreamingContent (
355+ content : string | object | Signal < string | object > | undefined
356+ ) : boolean {
357+ return isSignal ( content ) && ! this . isEmptyContent ( content ) ;
325358 }
326359
360+ /**
361+ * Helper method to get loading state from boolean or signal
362+ * If content (or signal content) is not empty but loading is true, assume streaming (no loading state shown)
363+ * If global loading is true and content is empty, show loading state
364+ */
327365 protected getLoadingState (
328366 messageLoading : boolean | Signal < boolean > | undefined ,
329- content : string | Signal < string > | undefined ,
367+ content : string | object | Signal < string | object > | undefined ,
330368 isLatest : boolean ,
331- globalLoading : boolean = false
369+ globalLoading : boolean = false ,
370+ allowEmptyContent : boolean = false
332371 ) : boolean {
333372 const messageLoadingValue = this . getLoadingValue ( messageLoading ) ;
334373 const isEmptyContent = this . isEmptyContent ( content ) ;
335374
375+ // If the content is empty, always show loading
376+ if ( isEmptyContent && ! allowEmptyContent ) {
377+ return true ;
378+ }
379+
336380 return messageLoadingValue || ( isLatest && globalLoading && isEmptyContent ) ;
337381 }
338382
339383 protected isLatestMessage ( message : ChatMessage ) : boolean {
340384 const messages = this . displayMessages ( ) ;
385+ if ( ! messages || messages . length === 0 ) return false ;
341386 return messages [ messages . length - 1 ] === message ;
342387 }
388+ private isLatestToolMessage ( message : ChatMessage ) : boolean {
389+ const messages = this . displayMessages ( ) ;
390+ if ( ! messages || messages . length === 0 ) return false ;
391+
392+ // Find the index of the message
393+ const messageIndex = messages . findIndex ( m => m === message ) ;
394+ if ( messageIndex === - 1 ) return false ;
395+
396+ // If it's the last message, it's the latest
397+ if ( messageIndex === messages . length - 1 ) return true ;
398+
399+ // If it's not second to last, it's not the latest
400+ if ( messageIndex < messages . length - 2 ) return false ;
401+
402+ // Check if the next (actually last) message is a loading message
403+ const nextMessage = messages [ messageIndex + 1 ] ;
404+
405+ // If next message is a single AI (or loading) message that just started, count this as latest
406+ if ( ! this . isTemplateMessage ( nextMessage ) && nextMessage . type === 'ai' ) return true ;
407+
408+ return false ;
409+ }
410+
411+ protected shouldAutoExpandInputArguments ( message : ChatMessage ) : boolean {
412+ if ( this . isTemplateMessage ( message ) || message . type !== 'tool' ) return false ;
413+ if ( ! message . autoExpandInputArguments ) {
414+ return false ;
415+ }
416+ return this . isLatestToolMessage ( message ) ;
417+ }
418+
419+ protected shouldAutoExpandOutput ( message : ChatMessage ) : boolean {
420+ if ( this . isTemplateMessage ( message ) || message . type !== 'tool' ) return false ;
421+ if ( ! message . autoExpandOutput ) {
422+ return false ;
423+ }
424+ return this . isLatestToolMessage ( message ) ;
425+ }
343426
344427 protected readonly isSignal = isSignal ;
345428}
0 commit comments