Skip to content

feat(chat): add generative UI blocks for maps and action buttons#6163

Open
eulicesl wants to merge 14 commits intoBasedHardware:mainfrom
eulicesl:feat/genui-chat-clean-room
Open

feat(chat): add generative UI blocks for maps and action buttons#6163
eulicesl wants to merge 14 commits intoBasedHardware:mainfrom
eulicesl:feat/genui-chat-clean-room

Conversation

@eulicesl
Copy link
Copy Markdown

Summary

  • add backend GenUI tools for inline map cards and action buttons
  • expose ui_blocks through the agentic pipeline and chat response model
  • add Flutter-side GenUI block parsing and chat rendering widgets
  • add targeted backend and Flutter tests for the new GenUI behavior

What changed

Backend

  • add create_map_ui and create_action_buttons_ui tools
  • export the tools and wire them into the core tool pipeline
  • collect ui_blocks from the agent/configurable path and pass them through the chat router
  • extend the backend Message model with ui_blocks

App

  • add GenUiBlock / GenUiBlockType parsing to ServerMessage
  • render inline GenUI blocks in AI chat messages
  • add MapCardWidget and ActionButtonsWidget

Validation

  • python3 -m py_compile ... on touched backend files
  • uv run --no-project --with pytest python -m pytest tests/unit/test_genui_tools.py -q
  • flutter analyze --no-fatal-infos ... on touched app files and tests
  • flutter test test/unit/genui_message_test.dart test/widgets/genui_widgets_test.dart

Notes

  • clean-room rebuild from current main
  • excluded unrelated iOS/signing/config/media churn from the historical working branch
  • includes a correctness fix in backend/utils/retrieval/agentic.py so ui_blocks are read from the existing configurable object
  • this PR should be reviewed from feat/genui-chat-clean-room; the old feat/genui-chat branch should be retired as the review surface

eulicesl and others added 10 commits March 29, 2026 13:14
Create LangChain tools that let the LLM render rich UI components
inline in chat: map cards with location data, and tappable action
buttons for follow-up questions.

(cherry picked from commit dbac65b)
Add create_map_ui and create_action_buttons_ui to CORE_TOOLS so the
LLM can autonomously decide when to show rich UI. Extract ui_blocks
from agent config after streaming, following the same pattern as
chart_data.

(cherry picked from commit 0ce919d)
Define GenUiBlockType enum (map, actionButtons) and GenUiBlock class
with JSON serialization. Add uiBlocks list to ServerMessage for
rendering generative UI components in chat.

(cherry picked from commit 24d100b)
MapCardWidget renders a static map image with title/description,
tappable to open native maps. ActionButtonsWidget renders a Wrap
of pill-shaped buttons that send follow-up messages when tapped.

(cherry picked from commit d855f17)
Render GenUiBlocks after chart data in AI messages. Add map shimmer
loading state when the LLM is thinking about locations. Thread
sendMessage callback through NormalMessageWidget for action buttons.

(cherry picked from commit 08ee7c1)
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 29, 2026

Greptile Summary

This PR adds generative UI (GenUI) blocks to the Omi chat experience, enabling the LLM to render inline map cards and action-button rows alongside text responses. The backend introduces two new LangChain tools (create_map_ui, create_action_buttons_ui) that write blocks to a shared request-scoped ContextVar; the agentic pipeline collects them after execution and serializes them through the existing callback_dataMessage path. The Flutter side adds typed parsing (GenUiBlock/GenUiBlockType), two new widgets, and a map-shimmer placeholder shown while the agent is thinking. Prior review concerns (circular-import shadow ContextVar, unknown-type coercion to map, fragile enum serialization, un-localized CTA string) are all cleanly addressed.

Key changes:

  • backend/utils/retrieval/context.py — new shared module for agent_config_context ContextVar, eliminating the circular-import risk
  • backend/utils/retrieval/tools/genui_tools.pycreate_map_ui and create_action_buttons_ui tools with coordinate validation and button-count capping
  • backend/utils/retrieval/agentic.py — GenUI tools added to CORE_TOOLS; ui_blocks extracted from configurable after agent run
  • app/lib/backend/schema/message.dartGenUiBlock/GenUiBlockType with wireValue-based serialization; unknown types are dropped
  • app/lib/pages/chat/widgets/genui_widgets.dartMapCardWidget (tappable static-map card) and ActionButtonsWidget (quick-reply buttons)
  • app/lib/l10n/tapToOpenInMaps localization key added and regenerated for all 37 locales

Issues found:

  • P1MapCardWidget.onTap fires an un-awaited async call to MapsUtil.launchMap; a MapLauncherException (no maps app installed) is silently dropped with no user feedback
  • P2 — The zoom parameter is documented by the backend tool, validated, and stored in the serialized block, but MapCardWidget never reads it — MapsUtil.getMapImageUrl always uses a hardcoded zoom=15

Confidence Score: 4/5

Safe to merge after fixing the un-awaited async map-launch call, which silently drops exceptions on devices without a maps app

One P1 issue remains: the onTap callback in MapCardWidget discards the Future returned by MapsUtil.launchMap, so any exception from the map launcher (e.g., no supported app installed) is silently swallowed. All previous review concerns have been addressed cleanly. The rest of the PR — context isolation, enum serialization, unknown-type filtering, localization — is solid.

app/lib/pages/chat/widgets/genui_widgets.dart — MapCardWidget.onTap needs await + error handling

Important Files Changed

Filename Overview
backend/utils/retrieval/tools/genui_tools.py New LangChain tools for map and action-button UI blocks; correctly mutates shared configurable dict via context var; zoom parameter is documented/stored but never consumed by the client widget
backend/utils/retrieval/context.py New shared module exporting agent_config_context ContextVar; correctly resolves the previously-flagged circular-import shadow-var issue
backend/utils/retrieval/agentic.py Wires genui tools into CORE_TOOLS and extracts ui_blocks from the configurable dict after agent execution; context set/read pattern is correct
app/lib/pages/chat/widgets/genui_widgets.dart New MapCardWidget and ActionButtonsWidget; MapCardWidget onTap fires an un-awaited async call with no error handling, risking silent failure when no maps app is available
app/lib/backend/schema/message.dart GenUiBlock/GenUiBlockType parsing added; unknown types are correctly dropped via whereType(); enum uses wireValue for safe serialization
app/lib/pages/chat/widgets/ai_message.dart GenUI blocks rendered inline below AI message text; map shimmer shown during typing when thinking contains 'map'/'location'; sendMessage callback threaded through correctly
backend/models/chat.py ui_blocks field added as Optional[List[dict]] to the Message model; straightforward additive change
backend/routers/chat.py ui_blocks extracted from callback_data and passed to Message constructor; symmetric with chart_data handling
backend/tests/unit/test_genui_tools.py Tests load genui_tools in isolation with stubbed @tool decorator; covers happy path, invalid coordinates, and button-count truncation
app/test/unit/genui_message_test.dart Covers JSON hydration, round-trip serialization, and unknown block type filtering; good coverage of addressed feedback
app/test/widgets/genui_widgets_test.dart Widget tests for ActionButtonsWidget covering tap-to-send and empty-buttons edge case; no widget test for MapCardWidget (network image makes it harder, acceptable)

Sequence Diagram

sequenceDiagram
    participant User
    participant Router as chat.py
    participant Agent as agentic.py
    participant Ctx as context.py
    participant Tools as genui_tools.py
    participant App as Flutter

    User->>Router: POST send_message
    Router->>Agent: execute_agentic_chat_stream
    Agent->>Ctx: set agent_config_context with configurable dict
    Agent->>Tools: invoke create_map_ui or create_action_buttons_ui
    Tools->>Ctx: get agent_config_context
    Ctx-->>Tools: configurable dict reference
    Tools->>Tools: append block to configurable ui_blocks list
    Tools-->>Agent: confirmation string
    Agent->>Agent: read configurable ui_blocks after task completes
    Agent-->>Router: callback_data including ui_blocks
    Router-->>User: Message JSON with ui_blocks
    User->>App: ServerMessage.fromJson
    App->>App: parse GenUiBlock list, drop unknown types
    App-->>User: render MapCardWidget or ActionButtonsWidget
Loading

Reviews (2): Last reviewed commit: "fix(chat): tighten genui review follow-u..." | Re-trigger Greptile

@eulicesl eulicesl marked this pull request as draft March 29, 2026 18:48
@eulicesl
Copy link
Copy Markdown
Author

Final readiness note for maintainers:

  • This PR remains the clean-room review surface for the GenUI feature work.
  • All actionable code review feedback on the current head has been addressed and the review threads are resolved.
  • The visible failed latest-head check was a GitHub Actions checkout/submodule setup failure during runner setup, not a lint/test failure in the GenUI changes themselves.
  • Feature-specific validation run for this branch:
    • python3 -m py_compile on touched backend files
    • uv run --no-project --with pytest python -m pytest backend/tests/unit/test_genui_tools.py -q
    • flutter analyze --no-fatal-infos on touched app files/tests
    • flutter test test/unit/genui_message_test.dart test/widgets/genui_widgets_test.dart

This PR is intentionally scoped and ready for maintainer review.

@eulicesl eulicesl marked this pull request as ready for review March 29, 2026 19:49
@eulicesl
Copy link
Copy Markdown
Author

Addressed the two remaining GenUI findings on the latest head:

  1. Map launch error handling

    • MapCardWidget.onTap now awaits MapsUtil.launchMap(...)
    • launch failures are surfaced to the user with a snackbar instead of being silently dropped
  2. Zoom handling

    • MapCardWidget now reads props['zoom']
    • MapsUtil.getMapImageUrl(...) now accepts and uses the provided zoom value instead of hardcoding 15

Validation re-run on the updated branch:

  • targeted flutter analyze --no-fatal-infos on the touched GenUI/map files ✅
  • flutter test test/unit/genui_message_test.dart test/widgets/genui_widgets_test.dart

Latest fix commit:

  • 6c4883badfix(chat): handle map launch errors and honor zoom

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant