Skip to content

feat(web): add streaming AI chat UI to the AI Insights page - #135

Open
YorgoTabet wants to merge 2 commits into
margin-nancy-jeanpaul-yorgo/developfrom
margin-nancy-jeanpaul-yorgo/feat/chat-frontend-ui
Open

feat(web): add streaming AI chat UI to the AI Insights page#135
YorgoTabet wants to merge 2 commits into
margin-nancy-jeanpaul-yorgo/developfrom
margin-nancy-jeanpaul-yorgo/feat/chat-frontend-ui

Conversation

@YorgoTabet

@YorgoTabet YorgoTabet commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

What & why

Final piece of the AI chat feature: the frontend. The streaming POST /chat endpoint and its read-only data tools already landed (#22/#55, #78) — but nothing in the UI talked to them. This wires the /ai-insights page up to that endpoint so owners can actually ask about their own records.

Per the product decision, the AI surface is the action-chip chat, so this replaces the old batch insight-generation UI on that page rather than adding a second AI screen.

Screenshots

Live conversation — grounded in real records

AI chat conversation

Both answers come from tool calls against the caller's own org: the summary reads businessSummary (revenue $15,520, gross margin 41.21%, net −$2,104 — a real loss despite healthy gross margin), and the expense list reads topNExpenses.

Empty state — action chips

AI chat empty state

How it works

  • useChat (@ai-sdk/react) over DefaultChatTransport pointed at the Nest endpoint with credentials: 'include' — the session cookie rides along so the backend AuthGuard scopes every answer to the caller's organization.
  • Renders UIMessage.parts in order: text as markdown, tool calls as "Checked your <label>" activity chips driven by part.state, plus typing dots and a stop control while streaming.
  • Four preset chips map to the existing read-only tools: businessSummary, topNExpenses, listSales/listProducts, listGoals.
  • Metrics stay computed in code — the model only interprets tool results.

Notes for reviewers

  • Version pin: @ai-sdk/react is pinned to the 3.x line, which is the one that pairs with ai@6 (matching apps/api). The 4.x line depends on ai@7 — don't @latest it.
  • react-markdown: the model replies in markdown; without a renderer the UI printed literal **asterisks**. Styled via explicit component overrides since this app doesn't use the Tailwind typography plugin.
  • Orphaned: the batch ai-insights backend module and hooks/use-ai-insights.ts are no longer surfaced but left in place — nothing deleted, easy to revert.

Verification

  • tsc --noEmit and eslint --max-warnings 0 clean on apps/web.
  • Driven end-to-end in a browser (Playwright): magic-link login → chip → live Gemini stream with real tool calls. Screenshots above are from that run.

Known limitation (not introduced here)

For questions that need ranking/arithmetic the model can occasionally reason over raw tool output and slip — e.g. asked "most profit per unit" it once picked a $65/unit item over a $420/unit one. The system prompt already says to read figures from tool results; tightening that (or adding a dedicated per-unit-margin tool) is backend work worth a follow-up.

Summary by CodeRabbit

  • New Features
    • Replaced precomputed AI insights with an interactive AI chat experience.
    • Added streaming responses, message history, stop generation, auto-scroll, and suggested prompts.
    • Added Markdown-formatted assistant responses and visible tool progress statuses.
    • Added loading, completion, and error indicators for AI-assisted actions.

Replaces the batch insight-generation page with a conversational,
action-chip chat wired to the existing POST /chat endpoint, so owners
can ask about their own sales, expenses, products and goals.

- useChat (@ai-sdk/react) over DefaultChatTransport pointed at the Nest
  endpoint with credentials: 'include', so the session cookie rides along
  and the backend AuthGuard scopes every answer to the caller's org.
- Renders UIMessage parts: text as markdown, tool calls as "Checked your
  <label>" activity chips driven by part.state, plus typing dots and a
  stop control while streaming.
- Four preset action chips map to the read-only data tools
  (businessSummary, topNExpenses, listSales/listProducts, listGoals).
- @ai-sdk/react pinned to the 3.x line, which is the one that pairs with
  ai@6 (the 4.x line targets ai@7).

Metrics stay computed in code; the model only interprets tool results.
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@YorgoTabet, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 44 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 02df1e00-f95b-4642-acec-2514f9fc5fd1

📥 Commits

Reviewing files that changed from the base of the PR and between 0ec6055 and 0664a66.

📒 Files selected for processing (1)
  • apps/web/app/(authenticated)/ai-insights/page.tsx
📝 Walkthrough

Walkthrough

The AI Insights page now provides an authenticated streaming chat UI using useChat, renders assistant markdown and tool activity, supports suggestions and message submission, and allows generation to be stopped.

Changes

AI Insights chat

Layer / File(s) Summary
Chat foundation and rendering contracts
apps/web/app/(authenticated)/ai-insights/page.tsx, apps/web/package.json
Adds chat configuration, suggestion and tool labels, markdown rendering with GFM support, and the AI SDK and markdown dependencies.
Message and tool activity rendering
apps/web/app/(authenticated)/ai-insights/page.tsx
Renders user and assistant transcript bubbles, markdown content, and tool progress states.
Chat request flow and composer
apps/web/app/(authenticated)/ai-insights/page.tsx
Connects useChat to the authenticated /chat endpoint, manages loading and errors, auto-scrolls messages, and wires suggestion, submit, and stop controls.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: chrisshaddad, jeanpaulgh

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AiInsightsPage
  participant ChatAPI
  User->>AiInsightsPage: Submit chat message
  AiInsightsPage->>ChatAPI: POST /chat with credentials
  ChatAPI-->>AiInsightsPage: Stream text and tool parts
  AiInsightsPage-->>User: Render markdown and tool status
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding a streaming AI chat UI to the AI Insights page.
Description check ✅ Passed The description covers the change, screenshots, implementation details, and verification, so it is mostly complete despite not matching the template exactly.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch margin-nancy-jeanpaul-yorgo/feat/chat-frontend-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/web/app/(authenticated)/ai-insights/page.tsx (1)

105-114: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use semantic heading tags for accessibility.

Rendering h1, h2, and h3 as <p> tags breaks the document outline for screen readers. You can maintain the exact same visual styling while using the correct semantic HTML elements.

♻️ Proposed fix
-  h1: ({ children }: { children?: React.ReactNode }) => (
-    <p className="text-sm font-semibold text-foreground">{children}</p>
-  ),
-  h2: ({ children }: { children?: React.ReactNode }) => (
-    <p className="text-sm font-semibold text-foreground">{children}</p>
-  ),
-  h3: ({ children }: { children?: React.ReactNode }) => (
-    <p className="text-sm font-semibold text-foreground">{children}</p>
-  ),
+  h1: ({ children }: { children?: React.ReactNode }) => (
+    <h1 className="text-sm font-semibold text-foreground">{children}</h1>
+  ),
+  h2: ({ children }: { children?: React.ReactNode }) => (
+    <h2 className="text-sm font-semibold text-foreground">{children}</h2>
+  ),
+  h3: ({ children }: { children?: React.ReactNode }) => (
+    <h3 className="text-sm font-semibold text-foreground">{children}</h3>
+  ),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/`(authenticated)/ai-insights/page.tsx around lines 105 - 114,
Update the Markdown heading renderers h1, h2, and h3 to emit their corresponding
semantic heading elements instead of p tags, while preserving the existing
text-sm font-semibold text-foreground styling and children handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/app/`(authenticated)/ai-insights/page.tsx:
- Around line 235-237: Update the scroll behavior in the useEffect that tracks
messages and waitingForReply to use immediate scrolling instead of smooth
scrolling. Keep the existing endRef.current?.scrollIntoView call and
dependencies unchanged so streaming updates remain anchored without repeatedly
restarting an animation.

---

Nitpick comments:
In `@apps/web/app/`(authenticated)/ai-insights/page.tsx:
- Around line 105-114: Update the Markdown heading renderers h1, h2, and h3 to
emit their corresponding semantic heading elements instead of p tags, while
preserving the existing text-sm font-semibold text-foreground styling and
children handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 05278c38-bba9-48d4-94d4-0de815515f49

📥 Commits

Reviewing files that changed from the base of the PR and between 9afcedf and 0ec6055.

⛔ Files ignored due to path filters (3)
  • docs/screenshots/ai-chat-conversation.png is excluded by !**/*.png
  • docs/screenshots/ai-chat-empty-state.png is excluded by !**/*.png
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • apps/web/app/(authenticated)/ai-insights/page.tsx
  • apps/web/package.json

Comment thread apps/web/app/(authenticated)/ai-insights/page.tsx
The scroll effect depends on `messages`, which changes on every streamed
chunk, so a smooth scroll restarted its animation many times a second and
visibly stuttered.

Uses 'instant' rather than 'auto': 'auto' defers to the CSS scroll-behavior
property, so it would silently regress if a global `scroll-behavior: smooth`
were ever added.

Verified in-browser: with the list overflowing (827px into a 331px viewport)
the view stays pinned to the bottom.
@YorgoTabet

Copy link
Copy Markdown
Collaborator Author

@AliMahmoudDev Thanks for looking — replying to both points.

Cancel button — already in there 👍

It's in the composer at page.tsx#L328-L337: while busy (status === 'submitted' || 'streaming') the send button swaps to a stop control wired to stop() from useChat, with aria-label="Stop generating".

Easy to miss from the PR — both screenshots are of the idle state, so they show the send button. I tried to grab a mid-stream shot to make it visible and hit the Gemini free-tier cap (limit: 20 requests, gemini-2.5-flash), so I'll add one once quota resets.

Reconnection mid-stream — real gap, needs backend work

Agreed, and it's genuinely missing. Right now a dropped connection surfaces the error state rather than resuming, so you lose the partial answer.

Doing it properly isn't frontend-only: the AI SDK's resume support (resumeStream / prepareReconnectToStreamRequest) needs the server to persist the stream and make it resumable by id — typically Redis-backed, which we already run. That's a chunk of apps/api work and out of scope for this PR, but worth a follow-up issue — happy to open one.

Logical properties / RTL — agree in principle, but not in this file alone

Two things worth flagging before we do this:

  1. We're on Tailwind v4 (4.1.18), where px-*/py-* already compile to padding-inline/padding-block — so the bulk of the spacing here is logical already. What's left physical is a handful of pl-5, border-l-2, rounded-tl-sm/rounded-br-sm.
  2. The app has no RTL support at all today — no dir attribute, no rtl: variants, nothing. And the codebase uses zero logical properties anywhere, against ~33 pl-/pr-/ml-/mr-, 8 border-l/r, and 2 rounded-tl/br in existing components.

So converting just this one file would make it the only file in the codebase using a different idiom, while delivering no actual RTL benefit — the sidebar, top nav, tables and every other page would still be LTR-physical, so the layout breaks under dir="rtl" regardless.

If RTL is on the roadmap I'm all for it, but as a deliberate app-wide pass (flip the shared components + add dir handling + an rtl: smoke test), not a one-file divergence here. Happy to open that as a separate issue if it's wanted — just don't want to half-do it in a way that reads inconsistent and buys nothing.

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