Skip to content

Inline Reply Suggestions

When you click an inbound message in the communication feed, an inline suggestions panel appears directly below it. The panel shows three AI-generated reply cards ranked by confidence, with controls to generate, regenerate, and use suggestions.

Click inbound message bubble
→ Toggle InlineSuggestionsPanel below the bubble
→ GET /api/text-agents/suggestions/{message_id}
→ If suggestions exist → render 3 cards with confidence badges
→ If none exist → show "Generate Suggestions" button
→ Click generate → POST /api/text-agents/generate-suggestions
→ Agentic AI loop produces 3 ranked replies
→ Click "Use This Reply" → populate compose bar with suggestion text

The panel cycles through five states depending on data availability and user actions:

StateTriggerDisplay
LoadingPanel opens, fetching dataThree skeleton placeholder cards
EmptyNo suggestions saved yetSparkles icon + “Generate Suggestions” button
GeneratingGenerate/regenerate clickedSpinner + “Generating suggestions…” + skeletons
LoadedSuggestions returnedThree cards in a responsive grid
ErrorAPI call failedError message + “Try Again” button

Each card displays three elements:

pub struct TextAgentSuggestionItem {
pub body: String, // The suggested reply text
pub confidence: f64, // Score between 0.0 and 1.0
}

Confidence badges use color-coded thresholds:

  • ≥ 80% — green badge (emerald-500)
  • 50–79% — blue badge (primary)
  • < 50% — gray badge (muted)

The “Use This Reply” button on each card calls the on_use handler, which populates the compose bar with that suggestion’s body text.

Inbound message bubbles gained interactive behaviors to support the suggestions panel:

  • Hover — a subtle ring highlight (hover:ring-1 ring-primary/30) and sparkles icon appear
  • Click — toggles the suggestions panel for that message
  • Selected state — active ring (ring-1 ring-primary/40) persists while the panel is open
  • Toggle — clicking the same bubble again closes the panel
  • Switch — clicking a different inbound bubble closes the previous panel and opens a new one

Selection resets automatically when you navigate to a different contact.

The communication feed now auto-scrolls to the most recent message when:

  • The conversation first loads
  • A new message arrives via WebSocket
  • You switch to a different contact

The scroll uses a reactive use_effect hook tied to the message list signal, with a short timeout to ensure the DOM has rendered before scrolling.

Both suggestion endpoints use contact-based access control instead of text agent management permissions. You need permission to view the contact — not permission to manage text agents.

The system calls check_contact_access with ContactInstanceAction::View, which checks:

  1. The contact exists in the caller’s organization
  2. The caller has either ContactInstancePermission::View (org-wide) or ContactInstancePermission::ViewAssigned (restricted to assigned contacts)

Super-admins and org owners bypass these checks automatically.

RoleCan see suggestions?
Super admin / org ownerAlways
Org admin with contact view permissionYes, for all contacts
Org member assigned to the contactYes, via ViewAssigned
Org member not assigned to the contactNo (403 Forbidden)

This means any team member who can view a contact’s conversation also sees AI suggestions for that contact’s messages — no separate text agent permission required.

GET /api/text-agents/suggestions/{message_id}
Body/Query: { contact_id: string }

Returns the most recent TextAgentSuggestion for the given message, or null if none exist. Requires ContactInstanceAction::View permission for the specified contact.

Status codes: 200 success, 400 invalid UUID, 403 no contact access, 500 server error.

POST /api/text-agents/generate-suggestions
Body: { contact_id: string, message_id: string }

Resolves the text agent assigned to the contact (via direct link or phone number fallback), runs the agentic suggestion loop, and persists the result to the text_agent_suggestion table. Requires ContactInstanceAction::View permission for the specified contact.

Status codes: 200 success, 400 invalid UUID, 403 no contact access, 404 message or contact not found, 500 server error.

ComponentFileRole
InlineSuggestionsPaneltext_agent/components/text_agent_suggestions_panel_component.rsPanel with fetch, generate, and card rendering
SuggestionCardSame file (private)Single card with confidence badge and use button
SkeletonCardsSame file (private)Three animated placeholder cards
MessageEntrymessaging/components/message_entry_component.rsBubble with click, hover, and selection states
CommunicationFeedmessaging/components/communication_feed_component.rsFeed container managing selection state and auto-scroll

Suggestions are persisted in the text_agent_suggestion table:

text_agent_suggestion (
id UUID PRIMARY KEY,
organization_id UUID NOT NULL,
contact_id UUID NOT NULL,
text_agent_id UUID NOT NULL,
message_id UUID NOT NULL,
suggestions JSONB NOT NULL, -- Vec<TextAgentSuggestionItem>
auto_sent_body TEXT, -- Set if auto-reply was triggered
created_at TIMESTAMP NOT NULL
)

Each generation creates a new row. The GET endpoint returns the most recent row for a given message_id, ordered by created_at DESC.