refactor(rtvi): clarify UI message names
This commit is contained in:
@@ -33,28 +33,26 @@ class RTVIUICommandFrame(SystemFrame):
|
|||||||
"""A frame for sending a UI command to the client.
|
"""A frame for sending a UI command to the client.
|
||||||
|
|
||||||
Pipeline-side counterpart of the ``ui-command`` RTVI message.
|
Pipeline-side counterpart of the ``ui-command`` RTVI message.
|
||||||
The observer wraps the ``command_name`` + ``payload`` into a
|
The observer wraps the ``command`` + ``payload`` into a
|
||||||
``UICommandMessage`` envelope before pushing it to the transport,
|
``UICommandMessage`` envelope before pushing it to the transport,
|
||||||
so the wire shape is:
|
so the wire shape is:
|
||||||
``{label, type: "ui-command", data: {name, payload}}``.
|
``{label, type: "ui-command", data: {command, payload}}``.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
command_name: App-defined command name (e.g. ``"toast"``,
|
command: App-defined command (e.g. ``"toast"``,
|
||||||
``"navigate"``, or any app-specific name). The wire
|
``"navigate"``, or any app-specific command).
|
||||||
field is ``data.name``; this avoids shadowing
|
|
||||||
``SystemFrame.name`` (the per-instance debug label).
|
|
||||||
payload: App-defined payload. Pydantic command models
|
payload: App-defined payload. Pydantic command models
|
||||||
(``Toast``, ``Navigate``, ``ScrollTo``, ...) should be
|
(``Toast``, ``Navigate``, ``ScrollTo``, ...) should be
|
||||||
converted to a plain dict via ``model_dump()`` before
|
converted to a plain dict via ``model_dump()`` before
|
||||||
being placed here; an arbitrary dict works as well.
|
being placed here; an arbitrary dict works as well.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
command_name: str = ""
|
command: str = ""
|
||||||
payload: Any = None
|
payload: Any = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""String representation of the UI command frame."""
|
"""String representation of the UI command frame."""
|
||||||
return f"{self.name}(command: {self.command_name})"
|
return f"{self.name}(command: {self.command})"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -97,17 +95,17 @@ class RTVIUIEventFrame(SystemFrame):
|
|||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
msg_id: The RTVI message id, as set by the client.
|
msg_id: The RTVI message id, as set by the client.
|
||||||
event_name: App-defined event name (the ``data.name`` field).
|
event: App-defined event (the ``data.event`` field).
|
||||||
payload: App-defined payload (the ``data.payload`` field).
|
payload: App-defined payload (the ``data.payload`` field).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
msg_id: str = ""
|
msg_id: str = ""
|
||||||
event_name: str = ""
|
event: str = ""
|
||||||
payload: Any = None
|
payload: Any = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""String representation of the UI event frame."""
|
"""String representation of the UI event frame."""
|
||||||
return f"{self.name}(event: {self.event_name})"
|
return f"{self.name}(event: {self.event})"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -595,11 +595,11 @@ class UIEventData(BaseModel):
|
|||||||
"""Inner ``data`` for a ``ui-event`` message.
|
"""Inner ``data`` for a ``ui-event`` message.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
name: App-defined event name.
|
event: App-defined event.
|
||||||
payload: App-defined payload, schemaless by design.
|
payload: App-defined payload, schemaless by design.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str
|
event: str
|
||||||
payload: Any | None = None
|
payload: Any | None = None
|
||||||
|
|
||||||
|
|
||||||
@@ -607,13 +607,13 @@ class UICommandData(BaseModel):
|
|||||||
"""Inner ``data`` for a ``ui-command`` message.
|
"""Inner ``data`` for a ``ui-command`` message.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
name: App-defined command name.
|
command: App-defined command.
|
||||||
payload: App-defined payload (already a plain dict by the
|
payload: App-defined payload (already a plain dict by the
|
||||||
time it lands on the wire). The standard payload models
|
time it lands on the wire). The standard command payload models
|
||||||
below produce the right shape via ``model_dump()``.
|
below produce the right shape via ``model_dump()``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str
|
command: str
|
||||||
payload: Any | None = None
|
payload: Any | None = None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ class RTVIObserver(BaseObserver):
|
|||||||
await self.send_rtvi_message(message)
|
await self.send_rtvi_message(message)
|
||||||
elif isinstance(frame, RTVIUICommandFrame):
|
elif isinstance(frame, RTVIUICommandFrame):
|
||||||
message = RTVI.UICommandMessage(
|
message = RTVI.UICommandMessage(
|
||||||
data=RTVI.UICommandData(name=frame.command_name, payload=frame.payload)
|
data=RTVI.UICommandData(command=frame.command, payload=frame.payload)
|
||||||
)
|
)
|
||||||
await self.send_rtvi_message(message)
|
await self.send_rtvi_message(message)
|
||||||
elif isinstance(frame, RTVIUITaskFrame):
|
elif isinstance(frame, RTVIUITaskFrame):
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ class RTVIProcessor(FrameProcessor):
|
|||||||
await self.push_frame(
|
await self.push_frame(
|
||||||
RTVIUIEventFrame(
|
RTVIUIEventFrame(
|
||||||
msg_id=message.id,
|
msg_id=message.id,
|
||||||
event_name=event_data.name,
|
event=event_data.event,
|
||||||
payload=event_data.payload,
|
payload=event_data.payload,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ with existing client code, and we want a test that fails loudly.
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from pipecat.processors.frameworks.rtvi.frames import (
|
||||||
|
RTVIUICommandFrame,
|
||||||
|
RTVIUIEventFrame,
|
||||||
|
)
|
||||||
from pipecat.processors.frameworks.rtvi.models import (
|
from pipecat.processors.frameworks.rtvi.models import (
|
||||||
Click,
|
Click,
|
||||||
Focus,
|
Focus,
|
||||||
@@ -43,27 +47,27 @@ class TestEnvelopeMessages(unittest.TestCase):
|
|||||||
"""Pin the on-the-wire envelope shapes for each first-class UI message."""
|
"""Pin the on-the-wire envelope shapes for each first-class UI message."""
|
||||||
|
|
||||||
def test_ui_event_envelope(self):
|
def test_ui_event_envelope(self):
|
||||||
msg = UIEventMessage(id="m1", data=UIEventData(name="nav_click", payload={"view": "home"}))
|
msg = UIEventMessage(id="m1", data=UIEventData(event="nav_click", payload={"view": "home"}))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
msg.model_dump(),
|
msg.model_dump(),
|
||||||
{
|
{
|
||||||
"label": "rtvi-ai",
|
"label": "rtvi-ai",
|
||||||
"type": "ui-event",
|
"type": "ui-event",
|
||||||
"id": "m1",
|
"id": "m1",
|
||||||
"data": {"name": "nav_click", "payload": {"view": "home"}},
|
"data": {"event": "nav_click", "payload": {"view": "home"}},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_ui_command_envelope_no_id(self):
|
def test_ui_command_envelope_no_id(self):
|
||||||
# Server-to-client push: no id field on the envelope (matches
|
# Server-to-client push: no id field on the envelope (matches
|
||||||
# ServerMessage / LLMFunctionCallMessage shape).
|
# ServerMessage / LLMFunctionCallMessage shape).
|
||||||
msg = UICommandMessage(data=UICommandData(name="toast", payload={"title": "Saved"}))
|
msg = UICommandMessage(data=UICommandData(command="toast", payload={"title": "Saved"}))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
msg.model_dump(),
|
msg.model_dump(),
|
||||||
{
|
{
|
||||||
"label": "rtvi-ai",
|
"label": "rtvi-ai",
|
||||||
"type": "ui-command",
|
"type": "ui-command",
|
||||||
"data": {"name": "toast", "payload": {"title": "Saved"}},
|
"data": {"command": "toast", "payload": {"title": "Saved"}},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -187,5 +191,20 @@ class TestPayloadShapes(unittest.TestCase):
|
|||||||
self.assertIsNone(payload["end_offset"])
|
self.assertIsNone(payload["end_offset"])
|
||||||
|
|
||||||
|
|
||||||
|
class TestFrames(unittest.TestCase):
|
||||||
|
"""Pin the frame API for named UI messages."""
|
||||||
|
|
||||||
|
def test_ui_command_frame_names_command_and_payload(self):
|
||||||
|
frame = RTVIUICommandFrame(command="toast", payload={"title": "Saved"})
|
||||||
|
self.assertEqual(frame.command, "toast")
|
||||||
|
self.assertEqual(frame.payload, {"title": "Saved"})
|
||||||
|
|
||||||
|
def test_ui_event_frame_names_event_and_payload(self):
|
||||||
|
frame = RTVIUIEventFrame(msg_id="m1", event="nav_click", payload={"view": "home"})
|
||||||
|
self.assertEqual(frame.msg_id, "m1")
|
||||||
|
self.assertEqual(frame.event, "nav_click")
|
||||||
|
self.assertEqual(frame.payload, {"view": "home"})
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user