@@ -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