@@ -36,12 +36,14 @@ class ChatPanel(Container):
3636
3737 _chat_widgets : dict [str , Markdown ]
3838 _last_update_time : dict [str , float ]
39+ _last_content : dict [str , str ]
3940
4041 def __init__ (self , ** kwargs ):
4142 """Initialize the chat panel."""
4243 super ().__init__ (** kwargs )
4344 self ._chat_widgets = {}
4445 self ._last_update_time = {}
46+ self ._last_content = {}
4547
4648 def compose (self ) -> ComposeResult :
4749 """Compose the UI layout."""
@@ -52,12 +54,13 @@ def compose(self) -> ComposeResult:
5254 id = "chat-input" ,
5355 )
5456
55- def update_messages (self , run : ExecutionRun ) -> None :
57+ def refresh (self , run : ExecutionRun ) -> None :
5658 """Update the chat panel with messages from the given execution run."""
5759 chat_view = self .query_one ("#chat-view" )
5860 chat_view .remove_children ()
5961 self ._chat_widgets .clear ()
6062 self ._last_update_time .clear ()
63+ self ._last_content .clear ()
6164
6265 for chat_msg in run .messages :
6366 self .add_chat_message (
@@ -76,7 +79,6 @@ def add_chat_message(
7679 chat_view = self .query_one ("#chat-view" )
7780
7881 message = chat_msg .message
79-
8082 if message is None :
8183 return
8284
@@ -118,23 +120,45 @@ def add_chat_message(
118120 now = time .monotonic ()
119121 last_update = self ._last_update_time .get (message .message_id , 0.0 )
120122
121- if existing :
123+ def should_update () -> bool :
122124 event = chat_msg .event
123- should_update = (
125+ finished = (
124126 event
125127 and event .exchange
126128 and event .exchange .message
127129 and event .exchange .message .end is not None
128130 )
129- if should_update or now - last_update > 0.15 :
131+ # Always update when the message is finished; otherwise throttle.
132+ return finished or (now - last_update > 0.15 )
133+
134+ if existing :
135+ if not should_update ():
136+ return
137+
138+ prev_content = self ._last_content .get (message .message_id , "" )
139+
140+ # Fast path: message is growing by appending new text.
141+ if isinstance (existing , Markdown ) and content .startswith (prev_content ):
142+ delta = content [len (prev_content ) :]
143+ if delta :
144+ # Streaming update: only append the new portion.
145+ existing .append (delta )
146+ else :
147+ # Fallback for non-monotonic changes: full update.
130148 existing .update (content )
131- self ._last_update_time [message .message_id ] = now
132- if auto_scroll :
133- chat_view .scroll_end (animate = False )
149+
150+ self ._last_content [message .message_id ] = content
151+ self ._last_update_time [message .message_id ] = now
152+
153+ if auto_scroll :
154+ chat_view .scroll_end (animate = False )
134155 else :
156+ # First time we see this message: create a new widget.
135157 widget_instance = widget_cls (content )
136158 chat_view .mount (widget_instance )
137159 self ._chat_widgets [message .message_id ] = widget_instance
138160 self ._last_update_time [message .message_id ] = now
161+ self ._last_content [message .message_id ] = content
162+
139163 if auto_scroll :
140164 chat_view .scroll_end (animate = False )
0 commit comments