1- import asyncio
1+ from dataclasses import dataclass
22from typing import TYPE_CHECKING
33
44from textual .widgets import Markdown
5- from textual .containers import VerticalScroll
65
76from agent_chat_cli .components .chat_history import ChatHistory
87from agent_chat_cli .components .messages import (
1817 from agent_chat_cli .app import AgentChatCLIApp
1918
2019
21- class MessageBus :
20+ @dataclass
21+ class StreamBuffer :
22+ widget : AgentMessageWidget | None = None
23+ text : str = ""
24+
25+ def reset (self ) -> None :
26+ self .widget = None
27+ self .text = ""
28+
29+
30+ class Renderer :
2231 def __init__ (self , app : "AgentChatCLIApp" ) -> None :
2332 self .app = app
24- self .current_agent_message : AgentMessageWidget | None = None
25- self .current_response_text = ""
33+ self ._stream = StreamBuffer ()
2634
27- async def handle_agent_message (self , message : AgentMessage ) -> None :
35+ async def render_message (self , message : AgentMessage ) -> None :
2836 match message .type :
2937 case AgentMessageType .STREAM_EVENT :
30- await self ._handle_stream_event (message )
38+ await self ._render_stream_event (message )
3139
3240 case AgentMessageType .ASSISTANT :
33- await self ._handle_assistant (message )
41+ await self ._render_assistant_message (message )
3442
3543 case AgentMessageType .SYSTEM :
36- await self ._handle_system (message )
44+ await self ._render_system_message (message )
3745
3846 case AgentMessageType .USER :
39- await self ._handle_user (message )
47+ await self ._render_user_message (message )
4048
4149 case AgentMessageType .TOOL_PERMISSION_REQUEST :
42- await self ._handle_tool_permission_request (message )
50+ await self ._render_tool_permission_request (message )
4351
4452 case AgentMessageType .RESULT :
45- await self ._handle_result ()
53+ await self ._on_complete ()
4654
47- async def _handle_stream_event (self , message : AgentMessage ) -> None :
55+ if message .type is not AgentMessageType .RESULT :
56+ await self .app .ui_state .scroll_to_bottom ()
57+
58+ async def _render_stream_event (self , message : AgentMessage ) -> None :
4859 text_chunk = message .data .get ("text" , "" )
4960
5061 if not text_chunk :
5162 return
5263
5364 chat_history = self .app .query_one (ChatHistory )
5465
55- if self .current_agent_message is None :
56- self .current_response_text = text_chunk
66+ if self ._stream . widget is None :
67+ self ._stream . text = text_chunk
5768
5869 agent_msg = AgentMessageWidget ()
5970 agent_msg .message = text_chunk
6071
61- # Append to chat history
62- chat_history .mount (agent_msg )
63- self .current_agent_message = agent_msg
72+ await chat_history .mount (agent_msg )
73+ self ._stream .widget = agent_msg
6474 else :
65- self .current_response_text += text_chunk
66-
67- markdown = self .current_agent_message .query_one (Markdown )
68- markdown .update (self .current_response_text )
75+ self ._stream .text += text_chunk
6976
70- await self ._scroll_to_bottom ()
77+ markdown = self ._stream .widget .query_one (Markdown )
78+ markdown .update (self ._stream .text )
7179
72- async def _handle_assistant (self , message : AgentMessage ) -> None :
80+ async def _render_assistant_message (self , message : AgentMessage ) -> None :
7381 content_blocks = message .data .get ("content" , [])
7482 chat_history = self .app .query_one (ChatHistory )
7583
7684 for block in content_blocks :
7785 block_type = block .get ("type" )
7886
7987 if block_type == ContentType .TOOL_USE .value :
80- if self .current_agent_message is not None :
81- self .current_agent_message = None
82- self .current_response_text = ""
88+ if self ._stream .widget is not None :
89+ self ._stream .reset ()
8390
8491 tool_name = block .get ("name" , "unknown" )
8592 tool_input = block .get ("input" , {})
@@ -88,24 +95,23 @@ async def _handle_assistant(self, message: AgentMessage) -> None:
8895 tool_msg .tool_name = tool_name
8996 tool_msg .tool_input = tool_input
9097
91- # Append to chat history
92- chat_history .mount (tool_msg )
93-
94- await self ._scroll_to_bottom ()
98+ await chat_history .mount (tool_msg )
9599
96- async def _handle_system (self , message : AgentMessage ) -> None :
100+ async def _render_system_message (self , message : AgentMessage ) -> None :
97101 system_content = (
98102 message .data if isinstance (message .data , str ) else str (message .data )
99103 )
104+
100105 await self .app .actions .add_message_to_chat (MessageType .SYSTEM , system_content )
101106
102- async def _handle_user (self , message : AgentMessage ) -> None :
107+ async def _render_user_message (self , message : AgentMessage ) -> None :
103108 user_content = (
104109 message .data if isinstance (message .data , str ) else str (message .data )
105110 )
111+
106112 await self .app .actions .add_message_to_chat (MessageType .USER , user_content )
107113
108- async def _handle_tool_permission_request (self , message : AgentMessage ) -> None :
114+ async def _render_tool_permission_request (self , message : AgentMessage ) -> None :
109115 log_json (
110116 {
111117 "event" : "showing_permission_prompt" ,
@@ -118,19 +124,9 @@ async def _handle_tool_permission_request(self, message: AgentMessage) -> None:
118124 tool_input = message .data .get ("tool_input" , {}),
119125 )
120126
121- await self ._scroll_to_bottom ()
122-
123- async def _handle_result (self ) -> None :
127+ async def _on_complete (self ) -> None :
124128 if not self .app .agent_loop .query_queue .empty ():
125129 return
126130
127131 self .app .ui_state .stop_thinking ()
128-
129- self .current_agent_message = None
130- self .current_response_text = ""
131-
132- async def _scroll_to_bottom (self ) -> None :
133- await asyncio .sleep (0.1 )
134-
135- container = self .app .query_one (VerticalScroll )
136- container .scroll_end (animate = False , immediate = True )
132+ self ._stream .reset ()
0 commit comments