@@ -17,6 +17,7 @@ type Message = {
1717  content_type : ContentType ; 
1818  operation : "append"  |  null ; 
1919} ; 
20+ 
2021type  ShinyChatMessage  =  { 
2122  id : string ; 
2223  handler : string ; 
@@ -30,6 +31,12 @@ type UpdateUserInput = {
3031  focus ?: false ; 
3132} ; 
3233
34+ type  StatusMessage  =  { 
35+   content : string ; 
36+   content_type : Exclude < ContentType ,  "markdown" > ; 
37+   replaceable : "true"  |  "false"  |  "" ; 
38+ } ; 
39+ 
3340// https://github.com/microsoft/TypeScript/issues/28357#issuecomment-748550734 
3441declare  global { 
3542  interface  GlobalEventHandlersEventMap  { 
@@ -39,11 +46,13 @@ declare global {
3946    "shiny-chat-clear-messages" : CustomEvent ; 
4047    "shiny-chat-update-user-input" : CustomEvent < UpdateUserInput > ; 
4148    "shiny-chat-remove-loading-message" : CustomEvent ; 
49+     "shiny-chat-append-status-message" : CustomEvent < StatusMessage > ; 
4250  } 
4351} 
4452
4553const  CHAT_MESSAGE_TAG  =  "shiny-chat-message" ; 
4654const  CHAT_USER_MESSAGE_TAG  =  "shiny-user-message" ; 
55+ const  CHAT_STATUS_MESSAGE_TAG  =  "shiny-status-message" ; 
4756const  CHAT_MESSAGES_TAG  =  "shiny-chat-messages" ; 
4857const  CHAT_INPUT_TAG  =  "shiny-chat-input" ; 
4958const  CHAT_CONTAINER_TAG  =  "shiny-chat-container" ; 
@@ -109,6 +118,32 @@ class ChatUserMessage extends LightElement {
109118  } 
110119} 
111120
121+ class  ChatStatusMessage  extends  LightElement  { 
122+   @property ( )  content  =  "" ; 
123+   @property ( )  content_type : Exclude < ContentType ,  "markdown" >  =  "text" ; 
124+   @property ( )  type : "dynamic"  |  "static"  =  "static" ; 
125+ 
126+   render ( )  { 
127+     const  content  = 
128+       this . content_type  ===  "html"  ? unsafeHTML ( this . content )  : this . content ; 
129+     return  html `${ content }  ; 
130+   } 
131+ 
132+   updated ( changedProperties : Map < string ,  unknown > )  { 
133+     super . updated ( changedProperties ) ; 
134+     if  ( 
135+       changedProperties . has ( "content" )  || 
136+       changedProperties . has ( "content_type" ) 
137+     )  { 
138+       this . #scrollIntoView( ) ; 
139+     } 
140+   } 
141+ 
142+   #scrollIntoView( )  { 
143+     this . scrollIntoView ( {  behavior : "smooth" ,  block : "end"  } ) ; 
144+   } 
145+ } 
146+ 
112147class  ChatMessages  extends  LightElement  { 
113148  render ( )  { 
114149    return  html `` ; 
@@ -262,7 +297,6 @@ class ChatInput extends LightElement {
262297} 
263298
264299class  ChatContainer  extends  LightElement  { 
265- 
266300  private  get  input ( ) : ChatInput  { 
267301    return  this . querySelector ( CHAT_INPUT_TAG )  as  ChatInput ; 
268302  } 
@@ -272,7 +306,7 @@ class ChatContainer extends LightElement {
272306  } 
273307
274308  private  get  lastMessage ( ) : ChatMessage  |  null  { 
275-     const  last  =  this . messages . lastElementChild ; 
309+     const  last  =  this . messages . querySelector ( "shiny-chat-message:last-child" ) ; 
276310    return  last  ? ( last  as  ChatMessage )  : null ; 
277311  } 
278312
@@ -290,6 +324,10 @@ class ChatContainer extends LightElement {
290324      "shiny-chat-append-message-chunk" , 
291325      this . #onAppendChunk
292326    ) ; 
327+     this . addEventListener ( 
328+       "shiny-chat-append-status-message" , 
329+       this . #onAppendStatus
330+     ) ; 
293331    this . addEventListener ( "shiny-chat-clear-messages" ,  this . #onClear) ; 
294332    this . addEventListener ( 
295333      "shiny-chat-update-user-input" , 
@@ -312,6 +350,10 @@ class ChatContainer extends LightElement {
312350      "shiny-chat-append-message-chunk" , 
313351      this . #onAppendChunk
314352    ) ; 
353+     this . removeEventListener ( 
354+       "shiny-chat-append-status-message" , 
355+       this . #onAppendStatus
356+     ) ; 
315357    this . removeEventListener ( "shiny-chat-clear-messages" ,  this . #onClear) ; 
316358    this . removeEventListener ( 
317359      "shiny-chat-update-user-input" , 
@@ -401,6 +443,20 @@ class ChatContainer extends LightElement {
401443    } 
402444  } 
403445
446+   #onAppendStatus( event : CustomEvent < StatusMessage > ) : void { 
447+     if  ( this . messages . lastChild  instanceof  ChatStatusMessage )  { 
448+       if  ( this . messages . lastChild . type  ==  "dynamic" )  { 
449+         // Update previous status message if last message was a status item 
450+         this . messages . lastChild . content  =  event . detail . content ; 
451+         this . messages . lastChild . content_type  =  event . detail . content_type ; 
452+         return ; 
453+       } 
454+     } 
455+ 
456+     const  status  =  createElement ( CHAT_STATUS_MESSAGE_TAG ,  event . detail ) ; 
457+     this . messages . appendChild ( status ) ; 
458+   } 
459+ 
404460  #onClear( ) : void { 
405461    this . messages . innerHTML  =  "" ; 
406462  } 
@@ -481,6 +537,7 @@ class ChatContainer extends LightElement {
481537
482538customElements . define ( CHAT_MESSAGE_TAG ,  ChatMessage ) ; 
483539customElements . define ( CHAT_USER_MESSAGE_TAG ,  ChatUserMessage ) ; 
540+ customElements . define ( CHAT_STATUS_MESSAGE_TAG ,  ChatStatusMessage ) ; 
484541customElements . define ( CHAT_MESSAGES_TAG ,  ChatMessages ) ; 
485542customElements . define ( CHAT_INPUT_TAG ,  ChatInput ) ; 
486543customElements . define ( CHAT_CONTAINER_TAG ,  ChatContainer ) ; 
0 commit comments