Skip to content

Commit ae9f0c8

Browse files
authored
Merge pull request #555 from UiPath/fix/chat_aggregations
fix: chat aggregations
2 parents 4932ac1 + 294d3a0 commit ae9f0c8

6 files changed

Lines changed: 68 additions & 54 deletions

File tree

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath"
3-
version = "2.1.37"
3+
version = "2.1.38"
44
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.10"

‎src/uipath/_cli/_dev/_terminal/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ def _handle_chat_event(
295295
if not updated_chat_message:
296296
return
297297
details_panel = self.app.query_one("#details-panel", RunDetailsPanel)
298-
details_panel.add_chat_message(updated_chat_message, execution_id)
298+
details_panel.add_chat_message(event, updated_chat_message, execution_id)
299299

300300
def _add_info_log(self, run: ExecutionRun, message: str):
301301
"""Add info log to run."""

‎src/uipath/_cli/_dev/_terminal/_components/_chat.py‎

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from textual.widgets import Input, Markdown
77

88
from uipath.agent.conversation import (
9+
UiPathConversationEvent,
910
UiPathConversationMessage,
1011
UiPathExternalValue,
1112
UiPathInlineValue,
@@ -17,11 +18,11 @@ class Prompt(Markdown):
1718

1819

1920
class Response(Markdown):
20-
BORDER_TITLE = "ai"
21+
BORDER_TITLE = "🤖 ai"
2122

2223

2324
class Tool(Markdown):
24-
BORDER_TITLE = "tool"
25+
BORDER_TITLE = "🛠️ tool"
2526

2627

2728
class ChatPanel(Container):
@@ -43,38 +44,40 @@ def compose(self) -> ComposeResult:
4344
id="chat-input",
4445
)
4546

46-
def add_chat_message(self, chat_msg: UiPathConversationMessage) -> None:
47+
def add_chat_message(
48+
self, event: UiPathConversationEvent, chat_msg: UiPathConversationMessage
49+
) -> None:
4750
"""Add or update a chat message bubble."""
4851
chat_view = self.query_one("#chat-view")
4952

5053
widget_cls: Union[type[Prompt], type[Response], type[Tool]]
5154
if chat_msg.role == "user":
5255
widget_cls = Prompt
53-
prefix = "👤"
5456
elif chat_msg.role == "assistant":
5557
widget_cls = Response
56-
prefix = "🤖"
5758
else:
5859
widget_cls = Response
59-
prefix = "⚒️"
6060

6161
parts: List[str] = []
6262
if chat_msg.content_parts:
6363
for part in chat_msg.content_parts:
64-
if part.mime_type.startswith("text/"):
64+
if (
65+
part.mime_type.startswith("text/")
66+
or part.mime_type == "application/json"
67+
):
6568
if isinstance(part.data, UiPathInlineValue):
6669
parts.append(part.data.inline or "")
6770
elif isinstance(part.data, UiPathExternalValue):
68-
# maybe fetch from URL or just show a placeholder
6971
parts.append(f"[external: {part.data.url}]")
7072

7173
text_block = "\n".join(parts).strip()
72-
content_lines = [f"{prefix} {text_block}"] if text_block else []
74+
content_lines = [f"{text_block}"] if text_block else []
7375

7476
if chat_msg.tool_calls:
77+
widget_cls = Tool
7578
for call in chat_msg.tool_calls:
76-
args = call.arguments
77-
content_lines.append(f"🛠 **{call.name}**\n{args}")
79+
status_icon = "✓" if call.result else "⚙"
80+
content_lines.append(f" {status_icon} **{call.name}**")
7881

7982
if not content_lines:
8083
return
@@ -86,7 +89,12 @@ def add_chat_message(self, chat_msg: UiPathConversationMessage) -> None:
8689
last_update = self._last_update_time.get(chat_msg.message_id, 0.0)
8790

8891
if existing:
89-
if now - last_update > 0.15:
92+
should_update = (
93+
event.exchange
94+
and event.exchange.message
95+
and event.exchange.message.end is not None
96+
)
97+
if should_update or now - last_update > 0.15:
9098
existing.update(content)
9199
self._last_update_time[chat_msg.message_id] = now
92100
chat_view.scroll_end(animate=False)

‎src/uipath/_cli/_dev/_terminal/_components/_details.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from textual.widgets import Input, RichLog, TabbedContent, TabPane, Tree
77
from textual.widgets.tree import TreeNode
88

9-
from uipath.agent.conversation import UiPathConversationMessage
9+
from uipath.agent.conversation import UiPathConversationEvent, UiPathConversationMessage
1010

1111
from .._models._execution import ExecutionRun
1212
from .._models._messages import LogMessage, TraceMessage
@@ -430,13 +430,16 @@ def add_log(self, log_msg: LogMessage):
430430
logs_log.write(log_msg.message)
431431

432432
def add_chat_message(
433-
self, chat_msg: UiPathConversationMessage, run_id: str
433+
self,
434+
event: UiPathConversationEvent,
435+
chat_msg: UiPathConversationMessage,
436+
run_id: str,
434437
) -> None:
435438
"""Add a chat message to the display."""
436439
if not self.current_run or run_id != self.current_run.id:
437440
return
438441
chat_panel = self.query_one("#chat-panel", ChatPanel)
439-
chat_panel.add_chat_message(chat_msg)
442+
chat_panel.add_chat_message(event, chat_msg)
440443

441444
def clear_display(self):
442445
"""Clear both traces and logs display."""

‎src/uipath/_cli/_dev/_terminal/_styles/terminal.tcss‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,15 +226,16 @@ TextArea.invalid {
226226

227227

228228
Prompt {
229-
background: $primary 5%;
229+
border: wide $primary-background;
230+
background: $surface;
230231
color: $text;
231232
margin-right: 8;
232233
margin-left: 1;
233234
padding: 1 1 0 1;
234235
}
235236
Response, Tool {
236-
border: wide $success;
237-
background: $success 5%;
237+
border: wide $primary-background;
238+
background: $surface;
238239
color: $text;
239240
margin: 1;
240241
margin-left: 8;

‎src/uipath/_cli/_dev/_terminal/_utils/_chat.py‎

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@ def _process_message_event(
7474
)
7575
self.messages[ev.message_id] = msg
7676

77-
# --- Handle content parts ---
77+
# --- Handle content parts (text, JSON, etc.) ---
7878
if ev.content_part:
7979
cp_event = ev.content_part
80+
8081
existing = next(
8182
(
8283
cp
@@ -86,27 +87,41 @@ def _process_message_event(
8687
None,
8788
)
8889

89-
if cp_event.start:
90+
# Start of a new content part
91+
if cp_event.start and not existing:
92+
new_cp = UiPathConversationContentPart(
93+
content_part_id=cp_event.content_part_id,
94+
mime_type=cp_event.start.mime_type,
95+
data=UiPathInlineValue(inline=""),
96+
citations=[],
97+
is_transcript=None,
98+
is_incomplete=True,
99+
)
100+
if msg.content_parts is None:
101+
msg.content_parts = []
102+
msg.content_parts.append(new_cp)
103+
existing = new_cp
104+
105+
# Chunk for an existing part (or backfill if start missing)
106+
if cp_event.chunk:
90107
if not existing:
108+
new_cp = UiPathConversationContentPart(
109+
content_part_id=cp_event.content_part_id,
110+
mime_type="text/plain", # fallback if start missing
111+
data=UiPathInlineValue(inline=""),
112+
citations=[],
113+
is_transcript=None,
114+
is_incomplete=True,
115+
)
91116
if msg.content_parts is None:
92117
msg.content_parts = []
93-
msg.content_parts.append(
94-
UiPathConversationContentPart(
95-
content_part_id=cp_event.content_part_id,
96-
mime_type=cp_event.start.mime_type,
97-
data=UiPathInlineValue(
98-
inline=cp_event.chunk.data
99-
if cp_event.chunk and cp_event.chunk.data
100-
else ""
101-
),
102-
citations=[],
103-
is_transcript=None,
104-
is_incomplete=None,
105-
)
106-
)
107-
if cp_event.chunk and existing:
118+
msg.content_parts.append(new_cp)
119+
existing = new_cp
120+
108121
if isinstance(existing.data, UiPathInlineValue):
109122
existing.data.inline += cp_event.chunk.data or ""
123+
124+
# End of a part
110125
if cp_event.end and existing:
111126
existing.is_incomplete = bool(cp_event.end.interrupted)
112127

@@ -122,43 +137,31 @@ def _process_message_event(
122137
None,
123138
)
124139

140+
# Start of a tool call
125141
if tc_event.start:
126142
if not existing_tool_call:
127-
# create new tool call
128143
new_tc = UiPathConversationToolCall(
129144
tool_call_id=tc_event.tool_call_id,
130145
name=tc_event.start.tool_name,
131-
arguments=tc_event.start.arguments,
146+
arguments=None, # args will arrive as JSON content part
132147
timestamp=tc_event.start.timestamp,
133148
result=None,
134149
)
135150
if msg.tool_calls is None:
136151
msg.tool_calls = []
137152
msg.tool_calls.append(new_tc)
153+
existing_tool_call = new_tc
138154
else:
139-
# merge/update existing
140155
existing_tool_call.name = (
141156
tc_event.start.tool_name or existing_tool_call.name
142157
)
143-
if tc_event.start.arguments:
144-
# If Inline, append more data
145-
if isinstance(
146-
tc_event.start.arguments, UiPathInlineValue
147-
) and isinstance(
148-
existing_tool_call.arguments, UiPathInlineValue
149-
):
150-
existing_tool_call.arguments.inline += (
151-
tc_event.start.arguments.inline or ""
152-
)
153-
else:
154-
existing_tool_call.arguments = tc_event.start.arguments
155158
existing_tool_call.timestamp = (
156159
tc_event.start.timestamp or existing_tool_call.timestamp
157160
)
158161

162+
# End of a tool call
159163
if tc_event.end:
160164
if not existing_tool_call:
161-
# Shouldn't happen, but initialize if end comes first
162165
existing_tool_call = UiPathConversationToolCall(
163166
tool_call_id=tc_event.tool_call_id,
164167
name="", # unknown until start seen
@@ -177,7 +180,6 @@ def _process_message_event(
177180

178181
# --- Update timestamps ---
179182
msg.updated_at = self._timestamp(ev)
180-
181183
return msg
182184

183185
def _timestamp(self, ev: UiPathConversationMessageEvent) -> str:

0 commit comments

Comments
 (0)