1717)
1818from weakref import WeakValueDictionary
1919
20- from htmltools import HTML , Tag , TagAttrValue , css , div
20+ from htmltools import HTML , Tag , TagAttrValue , css
2121
2222from .. import _utils , reactive
2323from .._deprecated import warn_deprecated
@@ -489,7 +489,12 @@ def messages(
489489
490490 return tuple (res )
491491
492- async def append_message (self , message : Any ) -> None :
492+ async def append_message (
493+ self ,
494+ message : Any ,
495+ * ,
496+ assistant_icon : HTML | Tag | None = None ,
497+ ):
493498 """
494499 Append a message to the chat.
495500
@@ -502,6 +507,9 @@ async def append_message(self, message: Any) -> None:
502507 Content strings are interpreted as markdown and rendered to HTML on the
503508 client. Content may also include specially formatted **input suggestion**
504509 links (see note below).
510+ assistant_icon
511+ An optional icon to display next to the assistant message. The icon can be
512+ any HTML element (e.g., an :func:`~shiny.ui.img` tag) or a string of HTML.
505513
506514 Note
507515 ----
@@ -531,10 +539,15 @@ async def append_message(self, message: Any) -> None:
531539 similar) is specified in model's completion method.
532540 ```
533541 """
534- await self ._append_message (message )
542+ await self ._append_message (message , icon = assistant_icon )
535543
536544 async def _append_message (
537- self , message : Any , * , chunk : ChunkOption = False , stream_id : str | None = None
545+ self ,
546+ message : Any ,
547+ * ,
548+ chunk : ChunkOption = False ,
549+ stream_id : str | None = None ,
550+ icon : HTML | Tag | None = None ,
538551 ) -> None :
539552 # If currently we're in a stream, handle other messages (outside the stream) later
540553 if not self ._can_append_message (stream_id ):
@@ -564,9 +577,18 @@ async def _append_message(
564577 if msg is None :
565578 return
566579 self ._store_message (msg , chunk = chunk )
567- await self ._send_append_message (msg , chunk = chunk )
580+ await self ._send_append_message (
581+ msg ,
582+ chunk = chunk ,
583+ icon = icon ,
584+ )
568585
569- async def append_message_stream (self , message : Iterable [Any ] | AsyncIterable [Any ]):
586+ async def append_message_stream (
587+ self ,
588+ message : Iterable [Any ] | AsyncIterable [Any ],
589+ * ,
590+ assistant_icon : HTML | Tag | None = None ,
591+ ):
570592 """
571593 Append a message as a stream of message chunks.
572594
@@ -579,6 +601,9 @@ async def append_message_stream(self, message: Iterable[Any] | AsyncIterable[Any
579601 OpenAI, Anthropic, Ollama, and others. Content strings are interpreted as
580602 markdown and rendered to HTML on the client. Content may also include
581603 specially formatted **input suggestion** links (see note below).
604+ assistant_icon
605+ An optional icon to display next to the assistant message. The icon can be
606+ any HTML element (e.g., an :func:`~shiny.ui.img` tag) or a string of HTML.
582607
583608 Note
584609 ----
@@ -614,7 +639,7 @@ async def append_message_stream(self, message: Iterable[Any] | AsyncIterable[Any
614639 # Run the stream in the background to get non-blocking behavior
615640 @reactive .extended_task
616641 async def _stream_task ():
617- await self ._append_message_stream (message )
642+ await self ._append_message_stream (message , icon = assistant_icon )
618643
619644 _stream_task ()
620645
@@ -627,11 +652,15 @@ async def _handle_error():
627652 await self ._raise_exception (e )
628653 _handle_error .destroy () # type: ignore
629654
630- async def _append_message_stream (self , message : AsyncIterable [Any ]):
655+ async def _append_message_stream (
656+ self ,
657+ message : AsyncIterable [Any ],
658+ icon : HTML | Tag | None = None ,
659+ ):
631660 id = _utils .private_random_id ()
632661
633662 empty = ChatMessage (content = "" , role = "assistant" )
634- await self ._append_message (empty , chunk = "start" , stream_id = id )
663+ await self ._append_message (empty , chunk = "start" , stream_id = id , icon = icon )
635664
636665 try :
637666 async for msg in message :
@@ -659,6 +688,7 @@ async def _send_append_message(
659688 self ,
660689 message : TransformedMessage ,
661690 chunk : ChunkOption = False ,
691+ icon : HTML | Tag | None = None ,
662692 ):
663693 if message ["role" ] == "system" :
664694 # System messages are not displayed in the UI
@@ -678,13 +708,17 @@ async def _send_append_message(
678708 content = message ["content_client" ]
679709 content_type = "html" if isinstance (content , HTML ) else "markdown"
680710
711+ # TODO: pass along dependencies for both content and icon (if any)
681712 msg = ClientMessage (
682713 content = str (content ),
683714 role = message ["role" ],
684715 content_type = content_type ,
685716 chunk_type = chunk_type ,
686717 )
687718
719+ if icon is not None :
720+ msg ["icon" ] = str (icon )
721+
688722 # print(msg)
689723
690724 await self ._send_custom_message (msg_type , msg )
@@ -1187,34 +1221,26 @@ def chat_ui(
11871221 raise ValueError ("Each message must be a string or a dictionary." )
11881222
11891223 if msg ["role" ] == "user" :
1190- msg_tag = Tag ( "shiny-user-message" , content = msg [ "content" ])
1224+ tag_name = "shiny-user-message"
11911225 else :
1192- msg_tag = Tag (
1193- "shiny-chat-message" ,
1194- (
1195- None
1196- if icon_assistant is None
1197- else div (icon_assistant , class_ = "message-icon" )
1198- ),
1199- content = msg ["content" ],
1200- )
1226+ tag_name = "shiny-chat-message"
12011227
1202- message_tags .append (msg_tag )
1228+ message_tags .append (Tag (tag_name , content = msg ["content" ]))
1229+
1230+ html_deps = None
1231+ if isinstance (icon_assistant , Tag ):
1232+ html_deps = icon_assistant .get_dependencies ()
12031233
12041234 res = Tag (
12051235 "shiny-chat-container" ,
1206- (
1207- None
1208- if icon_assistant is None
1209- else div (HTML (icon_assistant ), data_icon = "assistant" )
1210- ),
12111236 Tag ("shiny-chat-messages" , * message_tags ),
12121237 Tag (
12131238 "shiny-chat-input" ,
12141239 id = f"{ id } _user_input" ,
12151240 placeholder = placeholder ,
12161241 ),
12171242 chat_deps (),
1243+ html_deps ,
12181244 {
12191245 "style" : css (
12201246 width = as_css_unit (width ),
@@ -1224,6 +1250,7 @@ def chat_ui(
12241250 id = id ,
12251251 placeholder = placeholder ,
12261252 fill = fill ,
1253+ icon = str (icon_assistant ) if icon_assistant else None ,
12271254 ** kwargs ,
12281255 )
12291256
0 commit comments