feat(messages): AI suggested replies (LC-548) #518

Merged
longjacksonle merged 4 commits from feat/LC-548-suggest-reply into main 2026-07-07 18:41:08 +02:00

LC-548: AI suggested replies (Round 4, Vein B - next-gen AI)

A "Suggest reply" action on a received message drafts 2-3 tappable reply chips and drops the tapped one into the composer. Never auto-sent. Second action mode of the existing composer-LLM path (LC-532), so it reuses the same panel slot and apply/dismiss handlers.

How it works

  • New POST /messages/{id}/suggest-reply: gate on a configured LLM, enforce room access, assemble a short window of recent top-level turns ending at the target message (labelled by display name, system/empty turns skipped, bounded), and ask the operator model for a few short candidate replies from the viewer's point of view.
  • parse_suggestions turns the raw completion into clean chips: one per line, ordered/bullet list markers and a surrounding quote pair stripped, blanks dropped, deduped, capped at 3, over-long lines skipped.
  • The menu item lives in the per-message overflow menu, gated on [data-lc-llm] exactly like Translate, and hidden on your own / synthetic-actor messages (you reply to others, not yourself).
  • The result renders into #composer-ai-panel. Each chip is its own data-lc-ai-apply button carrying its full draft in a [data-lc-ai-suggestion] span; the shared live.js handler now prefers the chip-local suggestion and still falls back to the single-suggestion LC-532 assistant.

Trust / privacy posture

Same as compose_assist and summary: one complete() call, gated on an LLM being configured, room access enforced. Chat text leaves the device only on an explicit tap and only when an operator LLM is set. When no LLM is configured the endpoint 400s and the menu item is CSS-hidden.

Tests

  • Unit: parse_suggestions / strip_list_marker (list markers, quotes, dedup, cap, blanks) - run in CI (lib).
  • Integration routes_suggest_reply.rs: happy-path chips with markers stripped and per-chip suggestion spans, 404 on a missing message, 400 with no LLM.
  • Regenerated the LC-77 webhook/email render fixtures for the new conditional (empty-when-hidden) menu block: whitespace-only.

Gates: just check, full just test, just test-saas all green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz

## LC-548: AI suggested replies (Round 4, Vein B - next-gen AI) A "Suggest reply" action on a received message drafts 2-3 tappable reply chips and drops the tapped one into the composer. Never auto-sent. Second action mode of the existing composer-LLM path (LC-532), so it reuses the same panel slot and apply/dismiss handlers. ### How it works - New `POST /messages/{id}/suggest-reply`: gate on a configured LLM, enforce room access, assemble a short window of recent top-level turns ending at the target message (labelled by display name, system/empty turns skipped, bounded), and ask the operator model for a few short candidate replies from the viewer's point of view. - `parse_suggestions` turns the raw completion into clean chips: one per line, ordered/bullet list markers and a surrounding quote pair stripped, blanks dropped, deduped, capped at 3, over-long lines skipped. - The menu item lives in the per-message overflow menu, gated on `[data-lc-llm]` exactly like Translate, and hidden on your own / synthetic-actor messages (you reply to others, not yourself). - The result renders into `#composer-ai-panel`. Each chip is its own `data-lc-ai-apply` button carrying its full draft in a `[data-lc-ai-suggestion]` span; the shared `live.js` handler now prefers the chip-local suggestion and still falls back to the single-suggestion LC-532 assistant. ### Trust / privacy posture Same as `compose_assist` and `summary`: one `complete()` call, gated on an LLM being configured, room access enforced. Chat text leaves the device only on an explicit tap and only when an operator LLM is set. When no LLM is configured the endpoint 400s and the menu item is CSS-hidden. ### Tests - Unit: `parse_suggestions` / `strip_list_marker` (list markers, quotes, dedup, cap, blanks) - run in CI (lib). - Integration `routes_suggest_reply.rs`: happy-path chips with markers stripped and per-chip suggestion spans, 404 on a missing message, 400 with no LLM. - Regenerated the LC-77 webhook/email render fixtures for the new conditional (empty-when-hidden) menu block: whitespace-only. Gates: `just check`, full `just test`, `just test-saas` all green. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz
Add POST /messages/{id}/suggest-reply: gate on a configured LLM, enforce room access, assemble a short window of recent top-level turns ending at the target message, and ask the operator model for 2-3 short candidate replies from the viewer's point of view. parse_suggestions turns the raw completion into clean chips (one per line, list markers and surrounding quotes stripped, blanks dropped, deduped, capped). Same trust posture as compose_assist / summary: one complete() call, chat text leaves the device only on an explicit tap and only when an LLM is configured.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz
Add a "Suggest reply" item to the per-message overflow menu, gated on [data-lc-llm] like Translate and hidden on own / synthetic-actor messages (you reply to others, not yourself). It posts into #composer-ai-panel and renders a chip per draft. Each chip is its own data-lc-ai-apply button carrying its full text in a [data-lc-ai-suggestion] span; the shared live.js handler now prefers the chip-local suggestion, so tapping a chip drops that draft into the composer (never auto-sent) and falls back to the single-suggestion LC-532 assistant panel. Regenerate the LC-77 webhook/email render fixtures for the new (conditional, empty-when-hidden) menu block.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz
Add room-msg-suggest-reply and suggest-reply-heading in en and es, keeping the catalog parity the i18n_catalog test enforces.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz
test(routes): integration coverage for suggested replies (LC-548)
All checks were successful
check-secrets / Nosey parker (push) Successful in 5s
check-secrets / TruffleHog (push) Successful in 6s
check-secrets / Kingfisher (push) Successful in 9s
check-secrets / Kingfisher (pull_request) Successful in 8s
check-secrets / TruffleHog (pull_request) Successful in 8s
check-secrets / Nosey parker (pull_request) Successful in 9s
Check / clippy + fmt + tests (pull_request) Successful in 8m31s
Create release / Create release from merged PR (pull_request) Has been skipped
69142aa2d8
Drive /messages/{id}/suggest-reply over HTTP with a mock LLM: the happy path returns tappable chips with list markers stripped and per-chip data-lc-ai-suggestion spans; a missing message is 404; and with no LLM configured the endpoint 400s so room text is never forwarded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz
longjacksonle deleted branch feat/LC-548-suggest-reply 2026-07-07 18:41:09 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!518
No description provided.