44import json
55from datetime import datetime
66from pathlib import Path
7- from typing import Any
7+ from typing import Any , cast
88
99import pyperclip # type: ignore[import-untyped]
1010from textual import on
1818from uipath .dev .infrastructure import (
1919 patch_textual_stderr ,
2020)
21- from uipath .dev .models import ExecutionRun , LogMessage , TraceMessage
21+ from uipath .dev .models import (
22+ ChatMessage ,
23+ ExecutionMode ,
24+ ExecutionRun ,
25+ LogMessage ,
26+ TraceMessage ,
27+ )
28+ from uipath .dev .models .chat import get_user_message , get_user_message_event
2229from uipath .dev .services import RunService
2330from uipath .dev .ui .panels import NewRunPanel , RunDetailsPanel , RunHistoryPanel
2431
@@ -64,6 +71,7 @@ def __init__(
6471 on_run_updated = self ._on_run_updated ,
6572 on_log = self ._on_log_for_ui ,
6673 on_trace = self ._on_trace_for_ui ,
74+ on_chat = self ._on_chat_for_ui ,
6775 )
6876
6977 # Just defaults for convenience
@@ -96,9 +104,11 @@ async def on_button_pressed(self, event: Button.Pressed) -> None:
96104 if event .button .id == "new-run-btn" :
97105 await self .action_new_run ()
98106 elif event .button .id == "execute-btn" :
99- await self .action_execute_run ()
107+ await self .action_execute_run (mode = ExecutionMode . RUN )
100108 elif event .button .id == "debug-btn" :
101- await self .action_debug_run ()
109+ await self .action_execute_run (mode = ExecutionMode .DEBUG )
110+ elif event .button .id == "chat-btn" :
111+ await self .action_execute_run (mode = ExecutionMode .CHAT )
102112 elif event .button .id == "cancel-btn" :
103113 await self .action_cancel ()
104114 elif event .button .id == "debug-step-btn" :
@@ -135,9 +145,23 @@ async def handle_chat_input(self, event: Input.Submitted) -> None:
135145 return
136146
137147 if details_panel .current_run .status == "suspended" :
138- details_panel .current_run .resume_data = {"message" : user_text }
148+ details_panel .current_run .resume_data = {"value" : user_text }
149+ else :
150+ msg = get_user_message (user_text )
151+ msg_ev = get_user_message_event (
152+ user_text , conversation_id = details_panel .current_run .id
153+ )
154+ self ._on_chat_for_ui (
155+ ChatMessage (
156+ event = msg_ev ,
157+ message = msg ,
158+ run_id = details_panel .current_run .id ,
159+ )
160+ )
161+ details_panel .current_run .input_data = {"messages" : [msg ]}
139162
140163 asyncio .create_task (self ._execute_runtime (details_panel .current_run ))
164+
141165 event .input .clear ()
142166
143167 async def action_new_run (self ) -> None :
@@ -152,10 +176,10 @@ async def action_cancel(self) -> None:
152176 """Cancel and return to new run view."""
153177 await self .action_new_run ()
154178
155- async def action_execute_run (self ) -> None :
179+ async def action_execute_run (self , mode : ExecutionMode ) -> None :
156180 """Execute a new run based on NewRunPanel inputs."""
157181 new_run_panel = self .query_one ("#new-run-panel" , NewRunPanel )
158- entrypoint , input_data , conversational = new_run_panel .get_input_values ()
182+ entrypoint , input_data = new_run_panel .get_input_values ()
159183
160184 if not entrypoint :
161185 return
@@ -165,7 +189,7 @@ async def action_execute_run(self) -> None:
165189 except json .JSONDecodeError :
166190 return
167191
168- run = ExecutionRun (entrypoint , input_payload , conversational )
192+ run = ExecutionRun (entrypoint , input_payload , mode = mode )
169193
170194 history_panel = self .query_one ("#history-panel" , RunHistoryPanel )
171195 history_panel .add_run (run )
@@ -174,36 +198,10 @@ async def action_execute_run(self) -> None:
174198
175199 self ._show_run_details (run )
176200
177- if not run .conversational :
178- asyncio .create_task (self ._execute_runtime (run ))
179- else :
201+ if mode == ExecutionMode .CHAT :
180202 self ._focus_chat_input ()
181-
182- async def action_debug_run (self ) -> None :
183- """Execute a new run in debug mode (step-by-step)."""
184- new_run_panel = self .query_one ("#new-run-panel" , NewRunPanel )
185- entrypoint , input_data , conversational = new_run_panel .get_input_values ()
186-
187- if not entrypoint :
188- return
189-
190- try :
191- input_payload : dict [str , Any ] = json .loads (input_data )
192- except json .JSONDecodeError :
193- return
194-
195- # Create run with debug=True
196- run = ExecutionRun (entrypoint , input_payload , conversational , debug = True )
197-
198- history_panel = self .query_one ("#history-panel" , RunHistoryPanel )
199- history_panel .add_run (run )
200-
201- self .run_service .register_run (run )
202-
203- self ._show_run_details (run )
204-
205- # start execution in debug mode (it will pause immediately)
206- asyncio .create_task (self ._execute_runtime (run ))
203+ else :
204+ asyncio .create_task (self ._execute_runtime (run ))
207205
208206 async def action_debug_step (self ) -> None :
209207 """Step to next breakpoint in debug mode."""
@@ -267,6 +265,14 @@ def _on_trace_for_ui(self, trace_msg: TraceMessage) -> None:
267265 details_panel = self .query_one ("#details-panel" , RunDetailsPanel )
268266 details_panel .add_trace (trace_msg )
269267
268+ def _on_chat_for_ui (
269+ self ,
270+ chat_msg : ChatMessage ,
271+ ) -> None :
272+ """Append/refresh chat messages in the UI."""
273+ details_panel = self .query_one ("#details-panel" , RunDetailsPanel )
274+ details_panel .add_chat_message (chat_msg )
275+
270276 def _show_run_details (self , run : ExecutionRun ) -> None :
271277 """Show details panel for a specific run."""
272278 new_panel = self .query_one ("#new-run-panel" )
@@ -289,7 +295,9 @@ def _add_subprocess_log(self, level: str, message: str) -> None:
289295
290296 def add_log () -> None :
291297 details_panel = self .query_one ("#details-panel" , RunDetailsPanel )
292- run = getattr (details_panel , "current_run" , None )
298+ run : ExecutionRun = cast (
299+ ExecutionRun , getattr (details_panel , "current_run" , None )
300+ )
293301 if run :
294302 log_msg = LogMessage (run .id , level , message , datetime .now ())
295303 # Route through RunService so state + UI stay in sync
0 commit comments