Skip to content

refactor(dashboard): unify AI chat surfaces on assistant-ui Thread#1427

Open
mantrakp04 wants to merge 1 commit into
refactor/data-grid-and-dashboard-surfacesfrom
refactor/assistant-ui-chat-surfaces
Open

refactor(dashboard): unify AI chat surfaces on assistant-ui Thread#1427
mantrakp04 wants to merge 1 commit into
refactor/data-grid-and-dashboard-surfacesfrom
refactor/assistant-ui-chat-surfaces

Conversation

@mantrakp04
Copy link
Copy Markdown
Collaborator

Summary

  • Replace the bespoke ai-chat-shared chat UI (used by ask-ai, the stack companion widget, vibe coding chat, and the create-dashboard preview) with the shared assistant-ui Thread component.
  • Extract streaming request/format helpers into a new components/assistant-ui/chat-stream.ts module so each surface only owns its ChatModelAdapter.
  • Add a reusable ToolFallback for tool-call rendering and delete the now-unused ai-chat-shared.tsx (-1386 / +747 lines net).

Stacked on top of refactor/data-grid-and-dashboard-surfaces.

Test plan

  • pnpm lint
  • pnpm typecheck
  • Manually exercise each affected surface: command-center Ask AI, stack-companion widget, vibe-coding chat, analytics tables AI query, create-dashboard preview.

Replaces the bespoke ai-chat-shared chat UI used by ask-ai, the stack
companion widget, vibe coding chat, and the create-dashboard preview
with the shared assistant-ui Thread component. Extracts the streaming
request/format helpers into a new chat-stream module and the tool call
UI into a reusable ToolFallback.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment May 12, 2026 2:56am
stack-auth-mcp Ready Ready Preview, Comment May 12, 2026 2:56am
stack-backend Ready Ready Preview, Comment May 12, 2026 2:56am
stack-dashboard Ready Ready Preview, Comment May 12, 2026 2:56am
stack-demo Ready Ready Preview, Comment May 12, 2026 2:56am
stack-docs Ready Ready Preview, Comment May 12, 2026 2:56am
stack-preview-backend Ready Ready Preview, Comment May 12, 2026 2:56am
stack-preview-dashboard Ready Ready Preview, Comment May 12, 2026 2:56am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b7bf1559-ac11-452c-8c39-98712073dee8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/assistant-ui-chat-surfaces

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 and usage tips.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 12, 2026

Greptile Summary

This PR unifies four separate AI chat surfaces (ask-ai, stack-companion widget, vibe-coding chat, create-dashboard preview) onto the shared assistant-ui Thread component, and extracts the streaming transport, message formatting, and error classification logic into a new components/assistant-ui/chat-stream.ts module.

  • New chat-stream.ts exports createUnifiedAiTransport, sendAiStreamRequest, formatThreadMessagesForBackend, uiPartsToChatContent, and getFriendlyAiErrorMessage \u2014 reducing duplication across all AI surfaces.
  • thread.tsx gains welcome and assistantContentComponents props so each surface can inject custom empty-state UI and tool renderers without forking the component; the old bespoke ai-chat-shared.tsx (\u22121\u2009386 lines) is deleted.
  • New tool-fallback.tsx provides a reusable collapsible card for rendering streamed tool calls, handling running/complete/error states.

Confidence Score: 3/5

Safe to merge for most surfaces, but the analytics chat transport captures currentUser at creation time while the comment promises per-request freshness.

The analytics transport passes currentUser as a plain value and omits it from useMemo deps with a comment claiming liveness. That claim only holds when currentUser is a getter function. The dashboard preview sibling uses the getter pattern correctly. A session token refresh mid-session would leave the analytics surface sending stale auth credentials.

use-ai-query-chat.ts needs the ref+getter pattern for currentUser; chat-stream.ts should log dropped SSE parse failures.

Important Files Changed

Filename Overview
apps/dashboard/src/components/assistant-ui/chat-stream.ts New module extracting shared AI streaming helpers; parse failures are silently dropped in the SSE transform stream.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-query-chat.ts Simplified to use createUnifiedAiTransport, but passes currentUser as a plain value while the comment claims per-request freshness — risking stale auth headers.
apps/dashboard/src/components/assistant-ui/thread.tsx Adds welcome and assistantContentComponents props via context so callers can inject custom empty-state UI and tool renderers.
apps/dashboard/src/components/assistant-ui/tool-fallback.tsx New reusable collapsible card for rendering tool calls; handles running/complete/incomplete states cleanly.
apps/dashboard/src/components/vibe-coding/chat-adapters.ts Migrated to shared helpers; contains a duplicate sanitizeGeneratedCode also present in create-dashboard-preview.tsx.
apps/dashboard/src/components/commands/ask-ai.tsx Cleanly replaced bespoke chat UI with Thread + local runtime.
apps/dashboard/src/components/stack-companion/ai-chat-widget.tsx Migrated to Thread with persistence and conversation history; ref-based save queuing is well-structured.
apps/dashboard/src/components/commands/create-dashboard/create-dashboard-preview.tsx Correctly uses the ref+getter pattern for currentUser liveness, but duplicates sanitizeGeneratedCode.
apps/dashboard/src/components/assistant-ui/tooltip-icon-button.tsx Wraps TooltipContent in a Radix portal with collision padding to prevent viewport-edge clipping.
apps/dashboard/src/components/commands/ai-chat-shared.tsx Deleted — all functionality replaced by chat-stream.ts and the shared Thread component.

Sequence Diagram

sequenceDiagram
    participant Surface as UI Surface
    participant Thread as Thread component
    participant Adapter as ChatModelAdapter
    participant ChatStream as chat-stream.ts
    participant Backend as /api/latest/ai/query/stream

    Surface->>Thread: render(welcome, assistantContentComponents)
    Thread->>Adapter: "run({ messages, abortSignal })"
    Adapter->>ChatStream: formatThreadMessagesForBackend(messages)
    Adapter->>ChatStream: sendAiStreamRequest(baseUrl, user, body)
    ChatStream->>Backend: POST JSON + auth headers
    Backend-->>ChatStream: SSE UIMessageChunk stream
    ChatStream-->>Adapter: ReadableStream of UIMessageChunk
    Adapter->>ChatStream: readUIMessageStream + uiPartsToChatContent
    Adapter-->>Thread: "yield { content: ChatContent }"
    Thread-->>Surface: AssistantMessage via ToolFallback / MarkdownText
Loading

Comments Outside Diff (2)

  1. apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-query-chat.ts, line 110-126 (link)

    P1 Stale currentUser captured in transport despite comment claiming liveness

    currentUser is passed as a plain value to createUnifiedAiTransport, so resolveUser() inside the factory returns the value frozen at useMemo-creation time. The comment "current user is read via closure on each request" is only true when currentUser is passed as a getter function — the factory supports both shapes (CurrentUser | null | (() => CurrentUser | null)). In create-dashboard-preview.tsx, the sibling caller correctly uses () => currentUserRef.current to ensure auth headers are built from the live user on every request. If the user's session token is refreshed while this page is open, buildStackAuthHeaders will receive the stale user object and requests may fail with auth errors.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-query-chat.ts
    Line: 110-126
    
    Comment:
    **Stale `currentUser` captured in transport despite comment claiming liveness**
    
    `currentUser` is passed as a plain value to `createUnifiedAiTransport`, so `resolveUser()` inside the factory returns the value frozen at `useMemo`-creation time. The comment "current user is read via closure on each request" is only true when `currentUser` is passed as a getter function — the factory supports both shapes (`CurrentUser | null | (() => CurrentUser | null)`). In `create-dashboard-preview.tsx`, the sibling caller correctly uses `() => currentUserRef.current` to ensure auth headers are built from the live user on every request. If the user's session token is refreshed while this page is open, `buildStackAuthHeaders` will receive the stale user object and requests may fail with auth errors.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code Fix in Cursor Fix in Codex

  2. apps/dashboard/src/components/commands/create-dashboard/create-dashboard-preview.tsx, line 36-58 (link)

    P2 sanitizeGeneratedCode is duplicated from chat-adapters.ts

    This function (including the regex for semicolon-to-comma replacement and HTML entity decoding) is identical to the one defined in vibe-coding/chat-adapters.ts. Consider moving it to a shared utility in chat-stream.ts or a dedicated helper and importing it from both sites.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/dashboard/src/components/commands/create-dashboard/create-dashboard-preview.tsx
    Line: 36-58
    
    Comment:
    **`sanitizeGeneratedCode` is duplicated from `chat-adapters.ts`**
    
    This function (including the regex for semicolon-to-comma replacement and HTML entity decoding) is identical to the one defined in `vibe-coding/chat-adapters.ts`. Consider moving it to a shared utility in `chat-stream.ts` or a dedicated helper and importing it from both sites.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code Fix in Cursor Fix in Codex

Fix All in Claude Code Fix All in Cursor Fix All in Codex

Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-query-chat.ts:110-126
**Stale `currentUser` captured in transport despite comment claiming liveness**

`currentUser` is passed as a plain value to `createUnifiedAiTransport`, so `resolveUser()` inside the factory returns the value frozen at `useMemo`-creation time. The comment "current user is read via closure on each request" is only true when `currentUser` is passed as a getter function — the factory supports both shapes (`CurrentUser | null | (() => CurrentUser | null)`). In `create-dashboard-preview.tsx`, the sibling caller correctly uses `() => currentUserRef.current` to ensure auth headers are built from the live user on every request. If the user's session token is refreshed while this page is open, `buildStackAuthHeaders` will receive the stale user object and requests may fail with auth errors.

### Issue 2 of 3
apps/dashboard/src/components/assistant-ui/chat-stream.ts:140-155
**Silent drop of parse failures hides backend protocol errors**

When `parseJsonEventStream` fails to parse a SSE chunk (`parseResult.success === false`), the chunk is silently discarded with no logging of `parseResult.error` or `parseResult.rawValue`. In production, malformed backend responses will cause the stream to appear to end cleanly with no observable signal. A `captureError` call or at minimum a `console.warn` on the failure branch would make these failures visible.

### Issue 3 of 3
apps/dashboard/src/components/commands/create-dashboard/create-dashboard-preview.tsx:36-58
**`sanitizeGeneratedCode` is duplicated from `chat-adapters.ts`**

This function (including the regex for semicolon-to-comma replacement and HTML entity decoding) is identical to the one defined in `vibe-coding/chat-adapters.ts`. Consider moving it to a shared utility in `chat-stream.ts` or a dedicated helper and importing it from both sites.

Reviews (1): Last reviewed commit: "refactor(dashboard): unify AI chat surfa..." | Re-trigger Greptile

Comment on lines +140 to +155
}
}
return result;
}

export type WireMessage = { role: string, content: unknown };

/**
* `DefaultChatTransport` configured for the unified `/api/latest/ai/query/stream`
* endpoint. Shared by `useChat`-style callers (analytics, create-dashboard).
* `transformMessages` runs after `convertToModelMessages` and can prepend
* extra context messages.
*/
export function createUnifiedAiTransport(opts: {
backendBaseUrl: string,
/** Either a value (closed at creation) or a getter called at request time for liveness. */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Silent drop of parse failures hides backend protocol errors

When parseJsonEventStream fails to parse a SSE chunk (parseResult.success === false), the chunk is silently discarded with no logging of parseResult.error or parseResult.rawValue. In production, malformed backend responses will cause the stream to appear to end cleanly with no observable signal. A captureError call or at minimum a console.warn on the failure branch would make these failures visible.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/dashboard/src/components/assistant-ui/chat-stream.ts
Line: 140-155

Comment:
**Silent drop of parse failures hides backend protocol errors**

When `parseJsonEventStream` fails to parse a SSE chunk (`parseResult.success === false`), the chunk is silently discarded with no logging of `parseResult.error` or `parseResult.rawValue`. In production, malformed backend responses will cause the stream to appear to end cleanly with no observable signal. A `captureError` call or at minimum a `console.warn` on the failure branch would make these failures visible.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Cursor Fix in Codex

result.push({
type: "tool-call",
toolCallId: raw.toolCallId,
toolName: raw.toolName ?? "tool",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing validation for optional toolCallId field causes undefined values to be passed to type-required fields

Fix on Vercel

}

runAsynchronouslyWithAlert(doSave(messagesToSave, title));
const persist = useCallback((priorMessages: readonly ThreadMessage[], finalAssistantContent: ThreadAssistantContentPart[]) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
const persist = useCallback((priorMessages: readonly ThreadMessage[], finalAssistantContent: ThreadAssistantContentPart[]) => {
const persist = useCallback((priorMessages: readonly ThreadMessageLike[], finalAssistantContent: ThreadAssistantContentPart[]) => {

Type mismatch in persist() callback: expects ThreadMessage[] but receives ThreadMessageLike[] from ChatModelRunOptions

Fix on Vercel

result.push({
type: "tool-call",
toolCallId: raw.toolCallId,
toolName: raw.toolName ?? "tool",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing validation for optional toolCallId field causes undefined values to be passed to type-required fields

Fix on Vercel

}

runAsynchronouslyWithAlert(doSave(messagesToSave, title));
const persist = useCallback((priorMessages: readonly ThreadMessage[], finalAssistantContent: ThreadAssistantContentPart[]) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
const persist = useCallback((priorMessages: readonly ThreadMessage[], finalAssistantContent: ThreadAssistantContentPart[]) => {
const persist = useCallback((priorMessages: readonly ThreadMessageLike[], finalAssistantContent: ThreadAssistantContentPart[]) => {

Type mismatch in persist() callback: expects ThreadMessage[] but receives ThreadMessageLike[] from ChatModelRunOptions

Fix on Vercel

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