rename popup chat to floating chat
This commit is contained in:
@@ -44,7 +44,7 @@ from app.core.agent_runner import trigger_pending_runs
|
||||
from app.core.device_manager import device_manager
|
||||
from app.core.memory_middleware import MemoryMiddleware
|
||||
from app.core.orchestrator import orchestrate_v3_stream
|
||||
from app.core.output_formatter import HomeFormatter, PopupFormatter
|
||||
from app.core.output_formatter import HomeFormatter, FloatingFormatter
|
||||
from app.core.ws_context import clear_client_executor, set_client_executor
|
||||
from app.db import async_session
|
||||
from app.models import AgentRunLog
|
||||
@@ -183,9 +183,9 @@ async def _message_loop(websocket: WebSocket, user_id: str) -> None:
|
||||
_handle_home_request(websocket, user_id, frame)
|
||||
)
|
||||
|
||||
elif frame_type == WsFrameType.popup_request:
|
||||
elif frame_type == WsFrameType.floating_request:
|
||||
asyncio.create_task(
|
||||
_handle_popup_request(websocket, user_id, frame)
|
||||
_handle_floating_request(websocket, user_id, frame)
|
||||
)
|
||||
|
||||
elif frame_type == "pong":
|
||||
@@ -257,12 +257,12 @@ async def _handle_home_request(
|
||||
)
|
||||
|
||||
|
||||
async def _handle_popup_request(
|
||||
async def _handle_floating_request(
|
||||
websocket: WebSocket,
|
||||
user_id: str,
|
||||
frame: dict,
|
||||
) -> None:
|
||||
"""Handle a popup_request frame — streams PopupFormatter output back on the socket."""
|
||||
"""Handle a floating_request frame — streams FloatingFormatter output back on the socket."""
|
||||
request_id = frame.get("request_id") or str(uuid4())
|
||||
message: str = frame.get("message", "")
|
||||
session_id: str = frame.get("session_id") or str(uuid4())
|
||||
@@ -280,14 +280,14 @@ async def _handle_popup_request(
|
||||
response_chunks: list[str] = []
|
||||
try:
|
||||
token_stream = orchestrate_v3_stream(user_id, message, context)
|
||||
formatter = PopupFormatter(request_id=request_id)
|
||||
formatter = FloatingFormatter(request_id=request_id)
|
||||
async for ws_frame in formatter.format(token_stream):
|
||||
await websocket.send_text(ws_frame.model_dump_json())
|
||||
if ws_frame.type == "stream_text": # type: ignore[union-attr]
|
||||
response_chunks.append(ws_frame.chunk) # type: ignore[union-attr]
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"device_ws: popup_request failed user=%s req=%s: %s",
|
||||
"device_ws: floating_request failed user=%s req=%s: %s",
|
||||
user_id, request_id, exc,
|
||||
)
|
||||
finally:
|
||||
|
||||
@@ -166,7 +166,7 @@ async def orchestrate_v3_stream(
|
||||
"""v3 streaming orchestration — yields (agent_name, token) pairs.
|
||||
|
||||
The first yield always carries the agent_name with an empty token so that
|
||||
callers (e.g. PopupFormatter) can detect the routing domain before any text
|
||||
callers (e.g. FloatingFormatter) can detect the routing domain before any text
|
||||
tokens arrive.
|
||||
"""
|
||||
if reg is None:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Output Formatter — transforms orchestrator token streams into WS frame sequences.
|
||||
|
||||
HomeFormatter: produces stream_start, stream_text / stream_block, stream_end
|
||||
PopupFormatter: produces popup_domain, stream_text, stream_end
|
||||
FloatingFormatter: produces floating_domain, stream_text, stream_end
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -12,7 +12,7 @@ from collections.abc import AsyncGenerator
|
||||
from typing import Any
|
||||
|
||||
from app.schemas import (
|
||||
WsPopupDomain,
|
||||
WsFloatingDomain,
|
||||
WsStreamBlock,
|
||||
WsStreamEnd,
|
||||
WsStreamStart,
|
||||
@@ -24,7 +24,7 @@ logger = logging.getLogger(__name__)
|
||||
# Valid chart types (matching shadcn/ui Recharts wrappers in Electron)
|
||||
_VALID_CHART_TYPES = {"area", "bar", "line", "pie", "radar", "radial"}
|
||||
|
||||
# Map agent name → popup domain
|
||||
# Map agent name → floating domain
|
||||
_AGENT_DOMAIN: dict[str, str] = {
|
||||
"task_agent": "tasks",
|
||||
"checkpoint_agent": "checkpoints",
|
||||
@@ -32,7 +32,7 @@ _AGENT_DOMAIN: dict[str, str] = {
|
||||
"project_agent": "projects",
|
||||
}
|
||||
|
||||
WsFrame = WsStreamStart | WsStreamText | WsStreamBlock | WsStreamEnd | WsPopupDomain
|
||||
WsFrame = WsStreamStart | WsStreamText | WsStreamBlock | WsStreamEnd | WsFloatingDomain
|
||||
|
||||
|
||||
class HomeFormatter:
|
||||
@@ -191,11 +191,11 @@ class HomeFormatter:
|
||||
return matches if matches else None
|
||||
|
||||
|
||||
class PopupFormatter:
|
||||
class FloatingFormatter:
|
||||
"""Parses a token stream from orchestrate_v3_stream and yields WS frames.
|
||||
|
||||
Emits popup_domain immediately (from agent_name), then streams all tokens
|
||||
as plain stream_text — no block parsing for popup context.
|
||||
Emits floating_domain immediately (from agent_name), then streams all tokens
|
||||
as plain stream_text — no block parsing for floating context.
|
||||
"""
|
||||
|
||||
def __init__(self, request_id: str) -> None:
|
||||
@@ -210,7 +210,7 @@ class PopupFormatter:
|
||||
async for agent_name, token in token_stream:
|
||||
if not domain_sent:
|
||||
domain = _AGENT_DOMAIN.get(agent_name, "tasks")
|
||||
yield WsPopupDomain(
|
||||
yield WsFloatingDomain(
|
||||
request_id=self.request_id,
|
||||
domain=domain, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
@@ -174,12 +174,12 @@ class WsFrameType(str, Enum):
|
||||
device_hello = "device_hello"
|
||||
# ── v3 frame types ─────────────────────────────────────────────────
|
||||
home_request = "home_request"
|
||||
popup_request = "popup_request"
|
||||
floating_request = "floating_request"
|
||||
stream_start = "stream_start"
|
||||
stream_text = "stream_text"
|
||||
stream_block = "stream_block"
|
||||
stream_end = "stream_end"
|
||||
popup_domain = "popup_domain"
|
||||
floating_domain = "floating_domain"
|
||||
data_request = "data_request"
|
||||
data_response = "data_response"
|
||||
mutation = "mutation"
|
||||
@@ -263,8 +263,8 @@ class WsAgentComplete(BaseModel):
|
||||
|
||||
# ── WebSocket v3 Frame Models ─────────────────────────────────────────
|
||||
|
||||
class WsPopupScope(BaseModel):
|
||||
"""Scope for a popup request — narrows the agent to a specific entity."""
|
||||
class WsFloatingScope(BaseModel):
|
||||
"""Scope for a floating request — narrows the agent to a specific entity."""
|
||||
|
||||
type: Literal["task", "project", "note", "checkpoint"]
|
||||
id: str | None = None
|
||||
@@ -278,12 +278,12 @@ class WsHomeRequest(BaseModel):
|
||||
conversation_history: list[dict[str, Any]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class WsPopupRequest(BaseModel):
|
||||
"""Client → Server: Popup chat message scoped to an entity."""
|
||||
class WsFloatingRequest(BaseModel):
|
||||
"""Client → Server: Floating chat message scoped to an entity."""
|
||||
|
||||
type: Literal[WsFrameType.popup_request] = WsFrameType.popup_request
|
||||
type: Literal[WsFrameType.floating_request] = WsFrameType.floating_request
|
||||
message: str
|
||||
scope: WsPopupScope
|
||||
scope: WsFloatingScope
|
||||
|
||||
|
||||
class WsStreamStart(BaseModel):
|
||||
@@ -318,10 +318,10 @@ class WsStreamEnd(BaseModel):
|
||||
mutations: list[dict[str, Any]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class WsPopupDomain(BaseModel):
|
||||
"""Server → Client: domain determined for a popup request."""
|
||||
class WsFloatingDomain(BaseModel):
|
||||
"""Server → Client: domain determined for a floating request."""
|
||||
|
||||
type: Literal[WsFrameType.popup_domain] = WsFrameType.popup_domain
|
||||
type: Literal[WsFrameType.floating_domain] = WsFrameType.floating_domain
|
||||
request_id: str
|
||||
domain: Literal["tasks", "checkpoints", "notes", "projects"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user