From 76cf209edaea17b2ec172957df090824d13758eb Mon Sep 17 00:00:00 2001 From: ciregenz Date: Tue, 21 Jul 2026 18:40:19 -0700 Subject: [PATCH] [eric] chat: assistant answers drop the bubble (user-only bubbles); tool-ui cards get skeleton + blur-in entrance --- .../pages/AgentChat/tool-ui/ToolUiBubble.tsx | 17 +++++++++++- frontend/src/toolui/VendoredToolUi.tsx | 27 +++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/pages/AgentChat/tool-ui/ToolUiBubble.tsx b/frontend/src/app/pages/AgentChat/tool-ui/ToolUiBubble.tsx index e43acc62..59a68b68 100644 --- a/frontend/src/app/pages/AgentChat/tool-ui/ToolUiBubble.tsx +++ b/frontend/src/app/pages/AgentChat/tool-ui/ToolUiBubble.tsx @@ -26,7 +26,22 @@ function ToolUiBubble({ pair, sessionId, isPending, suppressReveal, sessionRunni ); } return ( - + ); diff --git a/frontend/src/toolui/VendoredToolUi.tsx b/frontend/src/toolui/VendoredToolUi.tsx index fdbbd0c3..00e9c8d0 100644 --- a/frontend/src/toolui/VendoredToolUi.tsx +++ b/frontend/src/toolui/VendoredToolUi.tsx @@ -60,6 +60,29 @@ function parseLeniently(schema: { safeParse: (v: unknown) => any }, props: Recor return { state: 'bad', problem: issues }; } +// Rough resting height per component family so the loading skeleton reserves believable space +// (Lobe/Open WebUI pattern: a breathing block where the card will land, not a tiny sliver). +function skeletonHeightFor(name: string): number { + if (/table|chart|gallery|map|carousel|post|terminal/.test(name)) return 180; + if (/stats|weather|plan|order|preferences|question/.test(name)) return 110; + return 56; +} + +const SkeletonBlock: React.FC<{ name: string }> = ({ name }) => ( +
+ +
+); + /** Validates against the upstream zod contract, then renders the vendored component inside the scoped theme. */ function VendoredToolUi({ name, props, extraProps }: VendoredToolUiProps): React.ReactElement | null { const { mode } = useThemeMode(); @@ -87,13 +110,13 @@ function VendoredToolUi({ name, props, extraProps }: VendoredToolUiProps): React ); } if (gate.state === 'pending') { - return
; + return ; } const Component = entry.Component; return (
- }> + }>