99
1010import pyperclip # type: ignore[import-untyped]
1111from rich .traceback import Traceback
12+ from textual import on
1213from textual .app import App , ComposeResult
1314from textual .binding import Binding
1415from textual .containers import Container , Horizontal
15- from textual .widgets import Button , Footer , ListView , RichLog
16+ from textual .widgets import Button , Footer , Input , ListView , RichLog
17+
18+ from uipath .agent .conversation import UiPathConversationEvent
1619
1720from ..._runtime ._contracts import (
1821 UiPathErrorContract ,
2427from ._components ._details import RunDetailsPanel
2528from ._components ._history import RunHistoryPanel
2629from ._components ._new import NewRunPanel
27- from ._components ._resume import ResumePanel
2830from ._models ._execution import ExecutionRun
2931from ._models ._messages import LogMessage , TraceMessage
30- from ._traces ._exporter import RunContextExporter
31- from ._traces ._logger import RunContextLogHandler
32+ from ._utils ._chat import RunContextChatHandler , build_user_message_event
33+ from ._utils ._exporter import RunContextExporter
34+ from ._utils ._logger import RunContextLogHandler
3235
3336
3437class UiPathDevTerminal (App [Any ]):
35- """UiPath development terminal interface."""
38+ """UiPath debugging terminal interface."""
3639
40+ TITLE = "UiPath Debugging Terminal"
41+ SUB_TITLE = "Interactive debugging interface for UiPath Python projects"
3742 CSS_PATH = Path (__file__ ).parent / "_styles" / "terminal.tcss"
3843
3944 BINDINGS = [
@@ -91,8 +96,6 @@ async def on_button_pressed(self, event: Button.Pressed) -> None:
9196 await self .action_execute_run ()
9297 elif event .button .id == "cancel-btn" :
9398 await self .action_cancel ()
94- elif event .button .id == "resume-btn" :
95- await self .action_resume ()
9699
97100 async def on_list_view_selected (self , event : ListView .Selected ) -> None :
98101 """Handle run selection from history."""
@@ -104,6 +107,31 @@ async def on_list_view_selected(self, event: ListView.Selected) -> None:
104107 if run :
105108 self ._show_run_details (run )
106109
110+ @on (Input .Submitted , "#chat-input" )
111+ async def handle_chat_input (self , event : Input .Submitted ) -> None :
112+ """Handle user submitting text into the chat."""
113+ user_text = event .value .strip ()
114+ if not user_text :
115+ return
116+
117+ details_panel = self .query_one ("#details-panel" , RunDetailsPanel )
118+ if details_panel and details_panel .current_run :
119+ if details_panel .current_run .status != "suspended" :
120+ self .app .notify (
121+ "Wait for agent response..." , timeout = 1.5 , severity = "warning"
122+ )
123+ return
124+ self ._handle_chat_event (
125+ build_user_message_event (
126+ user_text = user_text ,
127+ conversation_id = details_panel .current_run .id ,
128+ ),
129+ details_panel .current_run .id ,
130+ )
131+ details_panel .current_run .resume_data = {"value" : user_text }
132+ asyncio .create_task (self ._execute_runtime (details_panel .current_run ))
133+ event .input .clear ()
134+
107135 async def action_new_run (self ) -> None :
108136 """Show new run panel."""
109137 new_panel = self .query_one ("#new-run-panel" )
@@ -116,20 +144,6 @@ async def action_cancel(self) -> None:
116144 """Cancel and return to new run view."""
117145 await self .action_new_run ()
118146
119- async def action_resume (self ) -> None :
120- """Resume the suspended run."""
121- details_panel = self .query_one ("#details-panel" , RunDetailsPanel )
122- if details_panel and details_panel .current_run :
123- input : Dict [str , Any ] = {}
124- input_data = self .query_one ("#resume-panel" , ResumePanel ).get_input_values ()
125- try :
126- input = json .loads (input_data )
127- except json .JSONDecodeError :
128- return
129- details_panel .current_run .resume_data = input
130- asyncio .create_task (self ._execute_runtime (details_panel .current_run ))
131- details_panel .switch_tab ("run-tab" )
132-
133147 async def action_execute_run (self ) -> None :
134148 """Execute a new run with UiPath runtime."""
135149 new_run_panel = self .query_one ("#new-run-panel" , NewRunPanel )
@@ -179,7 +193,10 @@ async def _execute_runtime(self, run: ExecutionRun):
179193 execution_id = run .id ,
180194 logs_min_level = env .get ("LOG_LEVEL" , "INFO" ),
181195 log_handler = RunContextLogHandler (
182- run_id = run .id , on_log = self ._handle_log_message
196+ run_id = run .id , callback = self ._handle_log_message
197+ ),
198+ chat_handler = RunContextChatHandler (
199+ run_id = run .id , callback = self ._handle_chat_event
183200 ),
184201 )
185202
@@ -271,6 +288,15 @@ def _handle_log_message(self, log_msg: LogMessage):
271288 details_panel = self .query_one ("#details-panel" , RunDetailsPanel )
272289 details_panel .add_log (log_msg )
273290
291+ def _handle_chat_event (
292+ self , event : UiPathConversationEvent , execution_id : str
293+ ) -> None :
294+ updated_chat_message = self .runs [execution_id ].add_message (event )
295+ if not updated_chat_message :
296+ return
297+ details_panel = self .app .query_one ("#details-panel" , RunDetailsPanel )
298+ details_panel .add_chat_message (updated_chat_message , execution_id )
299+
274300 def _add_info_log (self , run : ExecutionRun , message : str ):
275301 """Add info log to run."""
276302 timestamp = datetime .now ()
0 commit comments