Skip to content

Conversation

@adityathebe
Copy link
Member

@adityathebe adityathebe commented Dec 19, 2025

Summary by CodeRabbit

  • New Features

    • Full AI chat experience (popover, streaming reasoning, tool approval, message branching, quick prompts).
    • Rich prompt input (attachments, drag/drop, submit states, optional speech-to-text).
    • New UI primitives and components (alerts, dialogs, popovers, selects, input groups, command palette, code blocks with copy, timeseries visuals, tool/debug panels).
  • Chores

    • Added npm config to bypass legacy peerDependency checks.
    • Lazy-loading of AI features to reduce initial bundle.
    • Dependency updates including AI SDKs and TypeScript 5.9.3.
  • Bug Fixes

    • Improved error parsing for JSON-formatted error messages.
  • Documentation

    • Guidance added: use the shadcn CLI for component generation.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 19, 2025

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

Project Deployment Review Updated (UTC)
aws-preview Ready Ready Preview Dec 24, 2025 10:37am
flanksource-ui Ready Ready Preview Dec 24, 2025 10:37am

@coderabbitai
Copy link

coderabbitai bot commented Dec 19, 2025

Walkthrough

Adds server-side LLM-backed chat endpoint, approval-aware MCP tool integration, many new AI UI components and providers (prompt input, messages, reasoning, popover chat), timeseries utilities, build/dependency updates, and supporting config/worker changes.

Changes

Cohort / File(s) Summary
Project config & deps
\.npmrc, AGENTS.md, components.json, tsconfig.json, package.json, next-env.d.ts
Adds legacy npm peer-deps flag, shadcn CLI instruction, AI registry entry, TypeScript/paths formatting updates, Next navigation types ref, and many AI/UI dependencies and scripts.
Server chat API
app/api/chat/route.ts
New POST endpoint: validates requests, resolves backend URL, fetches LLM connection, builds Anthropic/OpenAI model, integrates MCP client/tools, streams LLM responses, and handles errors.
Chat tools & approval logic
app/api/chat/tools.ts
Adds MCP tool types, approval-wrapping rules, plot_timeseries internal tool, and exported buildChatTools to fetch & augment tools.
AI feature loader & layout
src/ui/Layout/AiFeatureLoader.tsx, src/ui/Layout/SearchLayoutAiChat.tsx, src/ui/Layout/SearchLayout.tsx
Introduces AiFeatureLoaderProvider, AiFeatureRequest, lazy AI chat/button components, and refactors SearchLayout to wrap with AI loader provider and lazy AI children.
AI chat core & popover
src/components/ai/AiChat.tsx, src/components/ai/AiChatPopover.tsx
New AIChat UI with tool approval workflows, reasoning display and plot_timeseries rendering; AiChatPopover provider, hook, and popover component to manage chat state and quick prompts.
Prompt & message UI suite
src/components/ai-elements/*.tsx (e.g., code-block.tsx, prompt-input.tsx, message.tsx, conversation.tsx, reasoning.tsx, tool.tsx, confirmation.tsx, suggestion.tsx, shimmer.tsx, loader.tsx)
Large set of new client components: syntax-highlighted code blocks + copy, provider-backed PromptInput with attachments & speech, rich Message system with branching/attachments/actions, Reasoning panel, Tool panels, confirmation flows, suggestions, shimmer and loader primitives.
AI page & app integration
pages/ai.tsx, pages/_app.tsx, src/pages/config/details/SendToAiButton.tsx, src/pages/config/details/ConfigDetailsPage.tsx, src/components/Configs/ConfigDetailsTabs.tsx
Adds AI page, wraps app with AiChatPopoverProvider, adds SendToAi button that fetches config context and seeds chat, and injects lazy AI action into ConfigDetails header.
Base UI primitives (Radix wrappers)
src/components/ui/* (e.g., alert.tsx, button-group.tsx, command.tsx, dialog.tsx, hover-card.tsx, input-group.tsx, popover.tsx, select.tsx, textarea.tsx, button.tsx)
Adds many foundational UI components and variants (Dialog, Popover, HoverCard, Select, Command palette, InputGroup, Alert, ButtonGroup, Textarea) and minor button variant tweak.
Timeseries utilities & panel integration
src/lib/timeseries.ts, src/pages/audit-report/components/View/panels/TimeseriesPanel.tsx
New parseTimestamp and formatTick functions; TimeseriesPanel now imports and uses these utilities.
Error handling & mocks
src/components/ErrorViewer.tsx, public/mockServiceWorker.js
ErrorViewer parses JSON-formatted Error.message when possible; mockServiceWorker rewritten with simplified event handling, request/response messaging, and bypass logic.
Misc UI & components
components.json, next-env.d.ts, src/components/Configs/ConfigDetailsTabs.tsx, src/components/ErrorViewer.tsx
Minor metadata/typing changes, added extra prop to ConfigDetailsTabs, and ErrorViewer parsing enhancement.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend
    participant API as /api/chat (Server)
    participant MCP
    participant LLM

    User->>Frontend: Submit chat message
    Frontend->>API: POST /api/chat (messages + cookies)
    activate API

    rect rgba(230,245,250,0.9)
    API->>API: Validate body, emit metrics
    API->>API: Resolve backend URL (org metadata or env)
    API->>API: fetchLLMConnection (/connection/llm)
    API->>API: resolveModelId & buildLLMModel (Anthropic/OpenAI)
    end

    rect rgba(245,230,250,0.9)
    API->>MCP: Create MCP client (/mcp) with cookies
    API->>MCP: mcpClient.tools() -> fetch tools
    API->>API: wrap tools with approval + add plot_timeseries tool
    end

    rect rgba(240,250,230,0.9)
    API->>LLM: Start streaming chat (system prompt + tools)
    LLM-->>API: Stream reasoning & partial tokens
    API-->>Frontend: Stream UI-formatted response
    end

    deactivate API

    alt Tool call requiring approval
        Frontend->>User: Prompt for approval (via Confirmation UI)
        User-->>Frontend: Approve/Deny
        Frontend->>API: Submit approval decision
        API->>MCP: Execute approved tool
        MCP-->>API: Tool result
        API->>LLM: Continue chat with tool result
        LLM-->>Frontend: Final response
    end
Loading

Possibly related PRs

Suggested reviewers

  • moshloop

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.70% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Vercel AI SDK' is directly related to the main change: integration of Vercel AI SDK (including @AI-SDK packages, API routes, chat components, and AI-powered features) throughout the codebase.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/ai

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.

@netlify
Copy link

netlify bot commented Dec 19, 2025

Deploy Preview for clerk-saas-ui failed. Why did it fail? →

Name Link
🔨 Latest commit 6a7ecd7
🔍 Latest deploy log https://app.netlify.com/projects/clerk-saas-ui/deploys/694bc193e0d5e70008b2f3ed

@netlify
Copy link

netlify bot commented Dec 19, 2025

Deploy Preview for flanksource-demo-stable failed. Why did it fail? →

Name Link
🔨 Latest commit 6a7ecd7
🔍 Latest deploy log https://app.netlify.com/projects/flanksource-demo-stable/deploys/694bc1934837b800080e1773

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (17)
src/components/ui/carousel.tsx-107-119 (1)

107-119: Missing cleanup for "reInit" event listener.

The effect subscribes to both "reInit" and "select" events, but the cleanup function only unsubscribes from "select". This could cause a memory leak if the component is frequently mounted/unmounted.

🔎 Proposed fix
     return () => {
+      api?.off("reInit", onSelect);
       api?.off("select", onSelect);
     };
src/components/ai-elements/connection.tsx-12-18 (1)

12-18: Remove or define the "animated" CSS class.

The path element references a non-existent animated class. This class is not defined in the global styles, CSS modules, or Tailwind configuration. Either define this class or replace it with a valid Tailwind animation utility (e.g., animate-pulse, animate-spin from tailwindcss-animate).

src/components/ai-elements/edge.tsx-53-55 (1)

53-55: Add error handling or warning when handles are not found.

The function currently returns [0, 0] silently when a handle is not found, which can cause edges to render at the origin point without any indication of misconfiguration. This pattern is inconsistent with the explicit error thrown for invalid Position values (line 77). Consider logging a warning or throwing an error to help catch handle configuration issues during development.

app/api/chat/route.ts-82-86 (1)

82-86: Empty API key fallback may cause cryptic errors.

Using connection.password ?? "" as the API key will result in an empty string when credentials are missing, which may lead to obscure authentication errors from the provider. Consider validating the presence of credentials earlier.

🔎 Proposed validation
 function buildLLMModel(connection: LLMConnection): LanguageModelV3 {
+  if (!connection.password) {
+    throw new Error(`Missing API key for ${connection.type} provider`);
+  }
   const modelId = resolveModelId(connection);

   switch (connection.type) {
     case "anthropic": {
       const anthropicProvider = createAnthropic({
-        apiKey: connection.password ?? ""
+        apiKey: connection.password
       });

Committable suggestion skipped: line range outside the PR's diff.

app/api/chat/route.ts-139-142 (1)

139-142: MCP client not closed on caught exceptions.

If an error is caught in the outer try/catch (e.g., during createMCPClient or buildChatTools), the mcpClient may have been initialized but won't be closed.

🔎 Proposed fix using finally
   } catch (error) {
     console.error("Error handling chat request:", error);
+    await mcpClient?.close();
     return new Response("Internal Server Error", { status: 500 });
   }

Committable suggestion skipped: line range outside the PR's diff.

src/components/ai-elements/message.tsx-193-198 (1)

193-198: Move childrenArray creation inside useEffect and update dependencies.

Creating childrenArray in the render body causes it to have a new reference on every render, which means the useEffect runs unnecessarily due to shallow comparison of dependencies. React uses shallow comparison on the dependency array, triggering effects whenever reference changes.

Additionally, comparing only branches.length !== childrenArray.length won't detect when branch content changes, only when the count differs.

  const { currentBranch, setBranches, branches } = useMessageBranch();
- const childrenArray = Array.isArray(children) ? children : [children];
 
  // Use useEffect to update branches when they change
  useEffect(() => {
+   const childrenArray = Array.isArray(children) ? children : [children];
    if (branches.length !== childrenArray.length) {
      setBranches(childrenArray);
    }
- }, [childrenArray, branches, setBranches]);
+ }, [children, branches.length, setBranches]);

This approach:

  • Uses children directly as the dependency (the actual source of truth)
  • Setter functions have stable identity, so setBranches can be safely included without causing unnecessary runs
  • Avoids creating derived values in the render scope
  • Tracks length changes without creating new array references on each render
src/components/ui/button.tsx-27-28 (1)

27-28: icon-sm has identical dimensions to icon.

Both icon and icon-sm are defined with h-9 w-9. This appears to be a copy-paste oversight—icon-sm should likely have smaller dimensions (e.g., h-7 w-7 or h-8 w-8) to differentiate it from the standard icon size.

Suggested fix
         lg: "h-10 rounded-md px-8",
         icon: "h-9 w-9",
-        "icon-sm": "h-9 w-9"
+        "icon-sm": "h-7 w-7"
src/App.tsx-620-622 (1)

620-622: Wrap /ai route with authorization check for consistency.

The /ai route renders <AiChatPage /> directly without the withAuthorizationAccessCheck wrapper used throughout other protected routes. Either wrap it consistently with routes like /views and /catalogs, or clarify if public AI access is intentional.

src/components/ui/alert.tsx-35-45 (1)

35-45: Ref type mismatch: HTMLParagraphElement used for an h5 element.

The forwardRef generic specifies HTMLParagraphElement, but the component renders an h5 element which should use HTMLHeadingElement.

Proposed fix
 const AlertTitle = React.forwardRef<
-  HTMLParagraphElement,
+  HTMLHeadingElement,
   React.HTMLAttributes<HTMLHeadingElement>
 >(({ className, ...props }, ref) => (
src/components/ai-elements/checkpoint.tsx-48-71 (1)

48-71: className prop is destructured but never applied to the Button.

The className prop is destructured from props but not passed to either Button instance, causing any custom classes passed to CheckpointTrigger to be silently ignored.

Proposed fix
 export const CheckpointTrigger = ({
   children,
   className,
   variant = "ghost",
   size = "sm",
   tooltip,
   ...props
 }: CheckpointTriggerProps) =>
   tooltip ? (
     <Tooltip>
       <TooltipTrigger asChild>
-        <Button size={size} type="button" variant={variant} {...props}>
+        <Button size={size} type="button" variant={variant} className={className} {...props}>
           {children}
         </Button>
       </TooltipTrigger>
       <TooltipContent align="start" side="bottom">
         {tooltip}
       </TooltipContent>
     </Tooltip>
   ) : (
-    <Button size={size} type="button" variant={variant} {...props}>
+    <Button size={size} type="button" variant={variant} className={className} {...props}>
       {children}
     </Button>
   );
src/components/ui/input-group.tsx-73-78 (1)

73-78: Click-to-focus only works for input elements, not textarea.

The focus handler uses querySelector("input") which won't find textarea elements. When InputGroupTextarea is used, clicking the addon won't focus the textarea.

Proposed fix
       onClick={(e) => {
         if ((e.target as HTMLElement).closest("button")) {
           return;
         }
-        e.currentTarget.parentElement?.querySelector("input")?.focus();
+        const control = e.currentTarget.parentElement?.querySelector<HTMLElement>(
+          '[data-slot="input-group-control"]'
+        );
+        control?.focus();
       }}

Committable suggestion skipped: line range outside the PR's diff.

src/components/ai-elements/confirmation.tsx-16-42 (1)

16-42: Duplicate type variants in ToolUIPartApproval.

Lines 27-36 contain two identical variants with approved: true. This appears to be a copy-paste error.

🔎 Proposed fix - remove duplicate variant
 type ToolUIPartApproval =
   | {
       id: string;
       approved?: never;
       reason?: never;
     }
   | {
       id: string;
       approved: boolean;
       reason?: string;
     }
   | {
       id: string;
       approved: true;
       reason?: string;
     }
-  | {
-      id: string;
-      approved: true;
-      reason?: string;
-    }
   | {
       id: string;
       approved: false;
       reason?: string;
     }
   | undefined;
src/components/ai/AiChat.tsx-63-72 (1)

63-72: AddAttachmentButton renders an empty button without an icon or label.

The button has an aria-label but no visible content. Users won't see any indication of what this button does.

🔎 Proposed fix - add an attachment icon
+import { Paperclip } from "lucide-react";
+
 function AddAttachmentButton() {
   return (
     <PromptInputButton
       aria-label="Add attachment"
       size="icon-sm"
       type="button"
       variant="ghost"
-    />
+    >
+      <Paperclip className="size-4" />
+    </PromptInputButton>
   );
 }
src/components/ai-elements/reasoning.tsx-55-59 (1)

55-59: Type mismatch: isOpen should be narrowed to boolean.

The useControllableState hook returns T | undefined as its type signature, but ReasoningContextValue.isOpen (line 18) expects strictly boolean. At line 99, passing isOpen to the context provider and at line 104 to the Collapsible component will cause TypeScript type errors.

Although runtime is safe because defaultOpen = true always provides a value, add a type assertion (as boolean) or narrow the type to satisfy strict type checking.

src/components/ai-elements/context.tsx-63-68 (1)

63-68: Potential division by zero when maxTokens is 0.

If maxTokens is 0, the division usedTokens / maxTokens will result in Infinity or NaN, leading to incorrect rendering of the circular progress indicator.

🔎 Proposed fix
 const ContextIcon = () => {
   const { usedTokens, maxTokens } = useContextValue();
   const circumference = 2 * Math.PI * ICON_RADIUS;
-  const usedPercent = usedTokens / maxTokens;
+  const usedPercent = maxTokens > 0 ? usedTokens / maxTokens : 0;
   const dashOffset = circumference * (1 - usedPercent);
src/components/ai-elements/context.tsx-106-112 (1)

106-112: Same division by zero risk in ContextTrigger.

Apply the same guard here to prevent Infinity/NaN when maxTokens is 0.

🔎 Proposed fix
 export const ContextTrigger = ({ children, ...props }: ContextTriggerProps) => {
   const { usedTokens, maxTokens } = useContextValue();
-  const usedPercent = usedTokens / maxTokens;
+  const usedPercent = maxTokens > 0 ? usedTokens / maxTokens : 0;
   const renderedPercent = new Intl.NumberFormat("en-US", {
src/components/ai-elements/prompt-input.tsx-710-771 (1)

710-771: Form submission clears input optimistically before async completion.

The form is reset on line 724 before the async blob conversion and onSubmit complete. While text is captured beforehand, if the user types during the async operation, that input will be lost. The current pattern clears on success (lines 749-762), which contradicts the early reset.

The early form.reset() on line 724 only affects uncontrolled inputs. Since controlled mode via provider handles clearing separately (lines 751, 761), this may be intentional for the uncontrolled case. However, consider documenting this behavior or removing the early reset since clearing happens after success anyway.

🔎 Proposed fix: remove redundant early reset
-    // Reset form immediately after capturing text to avoid race condition
-    // where user input during async blob conversion would be lost
-    if (!usingProvider) {
-      form.reset();
-    }
-
     // Convert blob URLs to data URLs asynchronously
     Promise.all(

The clearing logic on lines 757-762 already handles the uncontrolled case after successful submission.

🧹 Nitpick comments (32)
src/components/ui/select.tsx (1)

13-31: Consider adding JSDoc comments for better developer experience.

While the component structure is self-explanatory, adding JSDoc comments with usage examples could improve the developer experience, especially for components with specific behaviors like SelectContent's positioning or SelectItem's indicator pattern.

Example:

/**
 * The trigger button for the select dropdown.
 * @example
 * <SelectTrigger>
 *   <SelectValue placeholder="Select an option" />
 * </SelectTrigger>
 */
const SelectTrigger = React.forwardRef<...
src/components/ai-elements/open-in-chat.tsx (3)

20-30: Unused github provider in registry.

The github provider is defined in the providers object, but there's no corresponding OpenInGitHub component exported. This appears to be dead code or an incomplete implementation.

Consider either:

  1. Adding an OpenInGitHub component if it's planned
  2. Removing the unused provider entry

259-261: Icons lack consistent sizing.

The icons are wrapped in <span className="shrink-0"> but don't have explicit size constraints. SVGs without className for sizing may render inconsistently since some have hardcoded dimensions while others (like MessageCircleIcon from lucide-react) rely on defaults.

Consider adding a consistent size class to the icon wrapper or directly to each icon:

Proposed fix
-        <span className="shrink-0">{providers.chatgpt.icon}</span>
+        <span className="size-4 shrink-0">{providers.chatgpt.icon}</span>

Or apply the size directly to each SVG in the providers object with className="size-4".


247-265: Consider reducing duplication with a generic provider component.

All six provider components share identical structure—only the provider key differs. While the explicit components offer good discoverability, the duplication could be reduced with a factory function.

Optional refactor to reduce duplication
const createOpenInProviderComponent = (
  providerKey: keyof typeof providers
) => {
  const Component = (props: ComponentProps<typeof DropdownMenuItem>) => {
    const { query } = useOpenInContext();
    const provider = providers[providerKey];
    return (
      <DropdownMenuItem asChild {...props}>
        <a
          className="flex items-center gap-2"
          href={provider.createUrl(query)}
          rel="noopener noreferrer"
          target="_blank"
        >
          <span className="size-4 shrink-0">{provider.icon}</span>
          <span className="flex-1">{provider.title}</span>
          <ExternalLinkIcon className="size-4 shrink-0" />
        </a>
      </DropdownMenuItem>
    );
  };
  Component.displayName = `OpenIn${providerKey.charAt(0).toUpperCase() + providerKey.slice(1)}`;
  return Component;
};

export const OpenInChatGPT = createOpenInProviderComponent("chatgpt");
export const OpenInClaude = createOpenInProviderComponent("claude");
// ... etc

Also applies to: 267-285, 287-305, 307-325, 327-345, 347-365

src/components/ui/carousel.tsx (1)

86-97: Consider adding ArrowUp/ArrowDown support for vertical orientation.

The keyboard handler only responds to ArrowLeft/ArrowRight. For vertical carousels, users might expect ArrowUp/ArrowDown to navigate. This is a minor UX enhancement.

🔎 Proposed enhancement
     const handleKeyDown = React.useCallback(
       (event: React.KeyboardEvent<HTMLDivElement>) => {
-        if (event.key === "ArrowLeft") {
+        if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
           event.preventDefault();
           scrollPrev();
-        } else if (event.key === "ArrowRight") {
+        } else if (event.key === "ArrowRight" || event.key === "ArrowDown") {
           event.preventDefault();
           scrollNext();
         }
       },
       [scrollPrev, scrollNext]
     );
src/components/ai-elements/task.tsx (1)

48-67: Consider conditionally applying asChild for better type safety.

The asChild prop is applied unconditionally on line 58. When asChild is true, Radix UI's CollapsibleTrigger expects a single React element child (enforced via React.Children.only()). However, the type system allows children: React.ReactNode, which includes strings, arrays, and fragments that would cause runtime errors.

While the default rendering (when children is not provided) works correctly, consumers could accidentally pass invalid children and encounter runtime errors.

🔎 Suggested improvement for type safety
 export const TaskTrigger = ({
   children,
   className,
   title,
   ...props
 }: TaskTriggerProps) => (
-  <CollapsibleTrigger asChild className={cn("group", className)} {...props}>
+  <CollapsibleTrigger 
+    asChild={!!children} 
+    className={cn("group", className)} 
+    {...props}
+  >
     {children ?? (
       <div className="flex w-full cursor-pointer items-center gap-2 text-sm text-muted-foreground transition-colors hover:text-foreground">
         <SearchIcon className="size-4" />
         <p className="text-sm">{title}</p>
         <ChevronDownIcon className="size-4 transition-transform group-data-[state=open]:rotate-180" />
       </div>
     )}
   </CollapsibleTrigger>
 );

This conditionally applies asChild only when custom children are provided, preventing potential runtime errors.

src/components/ai-elements/connection.tsx (1)

19-26: Consider using a CSS variable for the circle fill color.

The circle uses a hardcoded white color (fill="#fff"), which is inconsistent with the CSS variable usage for stroke colors and may not work well in dark mode or with different themes.

🔎 Suggested refactor to use a CSS variable
     <circle
       cx={toX}
       cy={toY}
-      fill="#fff"
+      fill="var(--color-background)"
       r={3}
       stroke="var(--color-ring)"
       strokeWidth={1}
     />

Note: Replace --color-background with the appropriate CSS variable from your design system.

src/components/ai-elements/image.tsx (2)

9-13: Consider documenting or removing unused uint8Array parameter.

The uint8Array parameter is destructured but never used. If this is intentional (to prevent it from being spread onto the img element), consider adding a comment. Otherwise, it can be removed to reduce confusion.


17-17: Redundant alt prop assignment.

The alt attribute is explicitly set to props.alt after spreading {...props}, which already includes alt. This is redundant since React will use the value from the spread. You can remove this line without changing behavior.

🔎 Proposed cleanup
   <img
     {...props}
-    alt={props.alt}
     className={cn(
       "h-auto max-w-full overflow-hidden rounded-md",
       props.className
     )}
     src={`data:${mediaType};base64,${base64}`}
   />

Note: With this change, you should also ensure the accessibility concern from the previous comment is addressed by providing a default alt value during destructuring or in the ImageProps type.

src/components/ai-elements/shimmer.tsx (1)

28-30: Potential type safety issue with component casting.

The cast to keyof JSX.IntrinsicElements assumes that the Component prop will always be an intrinsic HTML element. If a custom React component is passed via the as prop, this could cause type mismatches or runtime issues.

💡 Consider adding a type constraint or runtime validation

While this may work in practice, consider either:

  1. Constraining the as prop type to only accept intrinsic elements:
 export type TextShimmerProps = {
   children: string;
-  as?: ElementType;
+  as?: keyof JSX.IntrinsicElements;
   className?: string;
   duration?: number;
   spread?: number;
 };
  1. Or handling the type more defensively if custom components need to be supported.
src/components/ai-elements/edge.tsx (2)

86-103: Consider making edge direction configurable.

The function hard-codes Position.Right for source and Position.Left for target, which assumes a left-to-right flow. If edges might need to flow in other directions, consider accepting positions as parameters or deriving them from the edge configuration.

Potential refactor for flexibility
 const getEdgeParams = (
   source: InternalNode<Node>,
-  target: InternalNode<Node>
+  target: InternalNode<Node>,
+  sourcePos: Position = Position.Right,
+  targetPos: Position = Position.Left
 ) => {
-  const sourcePos = Position.Right;
   const [sx, sy] = getHandleCoordsByPosition(source, sourcePos);
-  const targetPos = Position.Left;
   const [tx, ty] = getHandleCoordsByPosition(target, targetPos);

   return {

131-131: Consider making animation duration configurable.

The animation duration is hard-coded to "2s". For better flexibility, consider accepting a duration prop or using a constant that can be easily adjusted.

Suggested refactor
-const Animated = ({ id, source, target, markerEnd, style }: EdgeProps) => {
+const Animated = ({ 
+  id, 
+  source, 
+  target, 
+  markerEnd, 
+  style,
+  data 
+}: EdgeProps) => {
   const sourceNode = useInternalNode(source);
   const targetNode = useInternalNode(target);

   if (!(sourceNode && targetNode)) {
     return null;
   }

   const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(
     sourceNode,
     targetNode
   );

   const [edgePath] = getBezierPath({
     sourceX: sx,
     sourceY: sy,
     sourcePosition: sourcePos,
     targetX: tx,
     targetY: ty,
     targetPosition: targetPos
   });

+  const duration = data?.animationDuration ?? "2s";
+
   return (
     <>
       <BaseEdge id={id} markerEnd={markerEnd} path={edgePath} style={style} />
       <circle fill="var(--primary)" r="4">
-        <animateMotion dur="2s" path={edgePath} repeatCount="indefinite" />
+        <animateMotion dur={duration} path={edgePath} repeatCount="indefinite" />
       </circle>
     </>
   );
 };
next.config.js (1)

9-9: LGTM! Production source maps disabled.

Disabling production browser source maps is a common practice that reduces bundle size and prevents source code exposure. However, this will make debugging production issues more challenging. Ensure you have adequate error tracking and logging in place (e.g., Sentry, LogRocket) to compensate for the loss of source maps.

app/api/chat/tools.ts (2)

29-69: Missing title parameter in input schema.

The output schema includes an optional title field, and the execute function returns a hardcoded title "Timeseries". However, the input schema doesn't include a title parameter, preventing users from customizing the chart title. Consider adding a title field to the input schema:

🔎 Proposed enhancement to support custom titles
 inputSchema: zodSchema(
   z.object({
     timeseries: z
       .array(
         z.object({
           timestamp: z
             .string()
             .describe("Timestamp in ISO-8601 format for the data point"),
           value: z.number().describe("Numeric value at the timestamp")
         })
       )
       .min(1)
-      .describe("Ordered list of data points to visualize")
+      .describe("Ordered list of data points to visualize"),
+    title: z.string().optional().describe("Optional title for the timeseries chart")
   })
 ),

And update the execute function:

-execute: async ({ timeseries }) => {
+execute: async ({ timeseries, title }) => {
   return {
     kind: "plot_timeseries",
     timeseries,
-    title: "Timeseries"
+    title: title ?? "Timeseries"
   };
 }

71-79: Consider adding error handling for MCP client calls.

The function calls mcpClient.tools() without error handling. If the MCP client fails to fetch tools (e.g., network error, timeout), the error will propagate to the caller. Consider whether explicit error handling or graceful fallback is appropriate for this use case.

🔎 Optional: Add error handling
 export async function buildChatTools(mcpClient: { tools: () => Promise<McpTools> }) {
-  const mcpTools = await mcpClient.tools();
+  let mcpTools: McpTools;
+  try {
+    mcpTools = await mcpClient.tools();
+  } catch (error) {
+    console.error("Failed to fetch MCP tools:", error);
+    // Fallback to empty tools or throw based on requirements
+    mcpTools = {};
+  }
+  
   const toolsWithApproval = wrapMcpToolsWithApproval(mcpTools);

   return {
     ...toolsWithApproval,
     plot_timeseries: createPlotTimeseriesTool()
   };
 }
app/api/chat/route.ts (1)

25-53: Consider adding timeout to Clerk API call.

The clerkClient.organizations.getOrganization call has no timeout. If the Clerk service is slow or unresponsive, this could block the request indefinitely.

src/components/ui/dialog.tsx (1)

43-48: Consider making the close button optional.

The close button is always rendered within DialogContent. Some dialogs (e.g., confirmation dialogs with explicit action buttons) may want to hide this default close button. Consider adding a prop like showCloseButton defaulting to true.

src/components/ai-elements/code-block.tsx (1)

155-162: Potential state update on unmounted component.

The setTimeout callback at line 159 could execute after the component unmounts, causing a React warning. Consider cleaning up the timeout.

🔎 Proposed fix with cleanup
 export const CodeBlockCopyButton = ({
   onCopy,
   onError,
   timeout = 2000,
   children,
   className,
   ...props
 }: CodeBlockCopyButtonProps) => {
   const [isCopied, setIsCopied] = useState(false);
   const { code } = useContext(CodeBlockContext);
+  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+
+  useEffect(() => {
+    return () => {
+      if (timeoutRef.current) {
+        clearTimeout(timeoutRef.current);
+      }
+    };
+  }, []);

   const copyToClipboard = async () => {
     if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
       onError?.(new Error("Clipboard API not available"));
       return;
     }

     try {
       await navigator.clipboard.writeText(code);
       setIsCopied(true);
       onCopy?.();
-      setTimeout(() => setIsCopied(false), timeout);
+      timeoutRef.current = setTimeout(() => setIsCopied(false), timeout);
     } catch (error) {
       onError?.(error as Error);
     }
   };
src/components/ai-elements/conversation.tsx (1)

79-81: Consider removing unnecessary useCallback.

The handleScrollToBottom wrapper only calls scrollToBottom() without additional logic. Since scrollToBottom is already stable from the context, the useCallback provides no memoization benefit and adds unnecessary indirection.

🔎 Simplified implementation
-  const handleScrollToBottom = useCallback(() => {
-    scrollToBottom();
-  }, [scrollToBottom]);
-
   return (
     !isAtBottom && (
       <Button
         className={cn(
           "absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full",
           className
         )}
-        onClick={handleScrollToBottom}
+        onClick={scrollToBottom}
         size="icon"
         type="button"
         variant="outline"
src/components/ai-elements/message.tsx (1)

218-236: Unused from prop in MessageBranchSelector.

The from prop is declared in the type (Line 215) and destructured (Line 220) but never used in the component. Consider removing it or implementing role-based styling if it was intended for visual differentiation between user and assistant branches.

🔎 If the prop is unnecessary
-export type MessageBranchSelectorProps = HTMLAttributes<HTMLDivElement> & {
-  from: UIMessage["role"];
-};
+export type MessageBranchSelectorProps = HTMLAttributes<HTMLDivElement>;
 
 export const MessageBranchSelector = ({
   className,
-  from,
   ...props
 }: MessageBranchSelectorProps) => {
src/App.tsx (1)

304-310: Consider adding a dedicated features.ai permission.

The AI navigation item reuses features.topology and tables.database for access control. If AI functionality should have distinct permissions (e.g., for billing, rate limiting, or feature flagging), consider adding dedicated entries to the features and tables objects.

package.json (1)

167-168: Increased heap size suggests growing bundle complexity.

The build now requires 8GB heap (--max-old-space-size=8192). This is quite high and may indicate bundle optimization opportunities. Consider running npm run analyze to identify large dependencies that could be code-split or lazy-loaded.

src/ui/Layout/SearchLayout.tsx (1)

99-116: Good accessibility implementation with one optional enhancement.

The trigger button has proper aria-expanded, title, and screen reader text. Consider adding aria-haspopup="dialog" to indicate the button opens a dialog-like popover.

Optional accessibility enhancement
         <button
           type="button"
           className="flex h-full w-8 items-center justify-center text-gray-400 hover:text-gray-500"
           title="AI Chat"
           aria-expanded={open}
+          aria-haspopup="dialog"
         >
src/pages/config/details/ConfigDetailsPage.tsx (2)

140-148: Consider simplifying error handling.

The error response is cast to any before passing to toastError, which bypasses type safety. Consider defining a proper error type or using a more specific type assertion that matches the ErrorMessage type expected by toastError.

Proposed simplification
       if (!response.ok) {
-        let message: unknown = `Failed to fetch AI context (${response.status})`;
-        try {
-          message = await response.json();
-        } catch {
-          // ignore parsing errors
-        }
-        toastError(message as any);
+        const fallbackMessage = `Failed to fetch AI context (${response.status})`;
+        try {
+          const errorJson = await response.json();
+          toastError(errorJson?.error || errorJson?.message || fallbackMessage);
+        } catch {
+          toastError(fallbackMessage);
+        }
         return;
       }

167-174: Minor: Unnecessary filter(Boolean) call.

All elements in the array are non-empty strings, so .filter(Boolean) has no effect here.

Proposed fix
       const text = [
         "Config details and it's related configs:",
         "```json",
         jsonText,
         "```"
       ]
-        .filter(Boolean)
         .join("\n\n");
src/components/ai-elements/checkpoint.tsx (1)

35-42: Clarify intent: CheckpointIcon returns children directly if provided.

The component returns children (which could be any React node) when provided, otherwise renders BookmarkIcon. This works but the behavior might be surprising since props like className are ignored when children exist. Consider documenting this or restructuring if the icon should wrap children.

src/components/ai/AiChatPopover.tsx (1)

72-83: Missing setQuickPrompts in useMemo dependency array.

The value object includes setQuickPrompts, but it's not listed in the dependency array. While setQuickPrompts is a state setter and stable, including it maintains consistency with React's exhaustive-deps expectations.

🔎 Proposed fix
   const value = useMemo(
     () => ({
       open,
       setOpen,
       chat,
       resetChat,
       setChatMessages,
       quickPrompts,
       setQuickPrompts
     }),
-    [open, setOpen, chat, resetChat, setChatMessages, quickPrompts]
+    [open, setOpen, chat, resetChat, setChatMessages, quickPrompts, setQuickPrompts]
   );
src/components/ai-elements/tool.tsx (1)

134-172: Consider guarding against non-renderable output values.

Line 144 casts output directly as ReactNode, but if output is a primitive like number, boolean, or null, it may render unexpectedly or not at all. The subsequent type checks handle object and string, but the fallback could be more defensive.

🔎 Proposed fix - add explicit handling for primitives
-  let Output = <div>{output as ReactNode}</div>;
+  let Output: ReactNode = null;
 
-  if (typeof output === "object" && !isValidElement(output)) {
+  if (output === null || output === undefined) {
+    Output = null;
+  } else if (typeof output === "object" && !isValidElement(output)) {
     Output = (
       <CodeBlock code={JSON.stringify(output, null, 2)} language="json" />
     );
   } else if (typeof output === "string") {
     Output = <CodeBlock code={output} language="json" />;
+  } else if (typeof output === "number" || typeof output === "boolean") {
+    Output = <CodeBlock code={String(output)} language="json" />;
+  } else {
+    Output = <div>{output as ReactNode}</div>;
   }
src/components/ai-elements/context.tsx (1)

142-158: Division by zero in ContextContentHeader and duplicate formatting logic.

The same maxTokens guard is needed here. Additionally, the percent/compact number formatting logic is duplicated across ContextIcon, ContextTrigger, and ContextContentHeader.

🔎 Proposed fix for division by zero
 export const ContextContentHeader = ({
   children,
   className,
   ...props
 }: ContextContentHeaderProps) => {
   const { usedTokens, maxTokens } = useContextValue();
-  const usedPercent = usedTokens / maxTokens;
+  const usedPercent = maxTokens > 0 ? usedTokens / maxTokens : 0;

Consider extracting shared formatting helpers (e.g., formatPercent, formatCompact) to reduce duplication.

src/components/ai-elements/model-selector.tsx (2)

168-181: External dependency on models.dev for provider logos.

The logos are fetched from an external URL (https://models.dev/logos/${provider}.svg). Consider the following:

  1. If models.dev is unavailable, logos will fail to load
  2. No fallback is provided for missing or failed images
🔎 Proposed enhancement with error handling
 export const ModelSelectorLogo = ({
   provider,
   className,
   ...props
 }: ModelSelectorLogoProps) => (
   <img
     {...props}
     alt={`${provider} logo`}
     className={cn("size-3 dark:invert", className)}
     height={12}
+    onError={(e) => {
+      e.currentTarget.style.display = 'none';
+    }}
     src={`https://models.dev/logos/${provider}.svg`}
     width={12}
   />
 );

104-166: Consider extracting the provider list to a shared constant.

The large union type for provider is comprehensive but will require maintenance. Extracting it to a shared constant would make it easier to update and reuse.

src/components/ai-elements/prompt-input.tsx (1)

1057-1109: Consider using Web Speech API types from @types/dom-speech-recognition.

Custom type definitions for SpeechRecognition are provided inline. The @types/dom-speech-recognition package provides official TypeScript definitions for the Web Speech API.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bfee1bc and ae187a7.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (55)
  • .env (1 hunks)
  • .npmrc (1 hunks)
  • AGENTS.md (1 hunks)
  • app/api/chat/route.ts (1 hunks)
  • app/api/chat/tools.ts (1 hunks)
  • components.json (1 hunks)
  • next-env.d.ts (1 hunks)
  • next.config.js (1 hunks)
  • package.json (7 hunks)
  • pages/_app.tsx (3 hunks)
  • pages/ai.tsx (1 hunks)
  • src/App.tsx (3 hunks)
  • src/components/Configs/ConfigDetailsTabs.tsx (3 hunks)
  • src/components/SearchSelect/index.tsx (0 hunks)
  • src/components/ai-elements/checkpoint.tsx (1 hunks)
  • src/components/ai-elements/code-block.tsx (1 hunks)
  • src/components/ai-elements/confirmation.tsx (1 hunks)
  • src/components/ai-elements/connection.tsx (1 hunks)
  • src/components/ai-elements/context.tsx (1 hunks)
  • src/components/ai-elements/controls.tsx (1 hunks)
  • src/components/ai-elements/conversation.tsx (1 hunks)
  • src/components/ai-elements/edge.tsx (1 hunks)
  • src/components/ai-elements/image.tsx (1 hunks)
  • src/components/ai-elements/loader.tsx (1 hunks)
  • src/components/ai-elements/message.tsx (1 hunks)
  • src/components/ai-elements/model-selector.tsx (1 hunks)
  • src/components/ai-elements/open-in-chat.tsx (1 hunks)
  • src/components/ai-elements/panel.tsx (1 hunks)
  • src/components/ai-elements/prompt-input.tsx (1 hunks)
  • src/components/ai-elements/queue.tsx (1 hunks)
  • src/components/ai-elements/reasoning.tsx (1 hunks)
  • src/components/ai-elements/shimmer.tsx (1 hunks)
  • src/components/ai-elements/suggestion.tsx (1 hunks)
  • src/components/ai-elements/task.tsx (1 hunks)
  • src/components/ai-elements/tool.tsx (1 hunks)
  • src/components/ai-elements/toolbar.tsx (1 hunks)
  • src/components/ai/AiChat.tsx (1 hunks)
  • src/components/ai/AiChatPopover.tsx (1 hunks)
  • src/components/ui/alert.tsx (1 hunks)
  • src/components/ui/button-group.tsx (1 hunks)
  • src/components/ui/button.tsx (1 hunks)
  • src/components/ui/carousel.tsx (1 hunks)
  • src/components/ui/command.tsx (1 hunks)
  • src/components/ui/dialog.tsx (1 hunks)
  • src/components/ui/hover-card.tsx (1 hunks)
  • src/components/ui/input-group.tsx (1 hunks)
  • src/components/ui/popover.tsx (1 hunks)
  • src/components/ui/progress.tsx (1 hunks)
  • src/components/ui/scroll-area.tsx (1 hunks)
  • src/components/ui/select.tsx (1 hunks)
  • src/components/ui/textarea.tsx (1 hunks)
  • src/pages/ai/AiChatPage.tsx (1 hunks)
  • src/pages/config/details/ConfigDetailsPage.tsx (3 hunks)
  • src/ui/Layout/SearchLayout.tsx (3 hunks)
  • tsconfig.json (2 hunks)
💤 Files with no reviewable changes (1)
  • src/components/SearchSelect/index.tsx
🧰 Additional context used
🧬 Code graph analysis (34)
src/components/ai-elements/image.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/panel.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/pages/ai/AiChatPage.tsx (2)
src/ui/Layout/SearchLayout.tsx (1)
  • SearchLayout (29-97)
src/components/ai/AiChat.tsx (1)
  • AIChat (150-472)
src/components/ui/alert.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/popover.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
pages/_app.tsx (1)
src/components/ai/AiChatPopover.tsx (1)
  • AiChatPopoverProvider (37-90)
src/components/ai-elements/shimmer.tsx (2)
src/api/types/topology.ts (1)
  • Component (27-37)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/hover-card.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/pages/config/details/ConfigDetailsPage.tsx (3)
src/components/ai/AiChatPopover.tsx (1)
  • useAiChatPopover (92-102)
src/components/Toast/toast.ts (1)
  • toastError (15-25)
src/ui/Buttons/Button.tsx (1)
  • Button (26-101)
app/api/chat/route.ts (1)
app/api/chat/tools.ts (1)
  • buildChatTools (71-79)
src/components/ai-elements/loader.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/progress.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/toolbar.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/checkpoint.tsx (4)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/separator.tsx (1)
  • Separator (29-29)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/tooltip.tsx (3)
  • Tooltip (30-30)
  • TooltipTrigger (30-30)
  • TooltipContent (30-30)
src/components/ai-elements/task.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/collapsible.tsx (3)
  • Collapsible (9-9)
  • CollapsibleTrigger (9-9)
  • CollapsibleContent (9-9)
src/components/ai-elements/conversation.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/select.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/App.tsx (3)
src/services/permissions/features.ts (1)
  • features (1-27)
src/context/UserAccessContext/permissions.ts (1)
  • tables (1-27)
src/pages/ai/AiChatPage.tsx (1)
  • AiChatPage (6-14)
src/components/ai-elements/code-block.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/carousel.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/input-group.tsx (4)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/input.tsx (1)
  • Input (22-22)
src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
src/ui/Layout/SearchLayout.tsx (2)
src/components/ai/AiChatPopover.tsx (3)
  • AiChatPopoverProvider (37-90)
  • useAiChatPopover (92-102)
  • AiChatPopover (110-151)
src/ui/Layout/Preference.tsx (1)
  • PreferencePopOver (53-67)
src/components/ui/scroll-area.tsx (2)
src/hooks/usePrevious.js (1)
  • ref (4-4)
src/lib/utils.ts (1)
  • cn (4-6)
pages/ai.tsx (2)
src/App.tsx (2)
  • CanaryCheckerApp (1025-1056)
  • App (1113-1135)
src/components/Authentication/AuthSessionChecker.tsx (1)
  • AuthSessionChecker (9-19)
src/components/ai/AiChat.tsx (11)
src/components/ai-elements/prompt-input.tsx (7)
  • PromptInputButton (959-977)
  • PromptInput (458-804)
  • PromptInputBody (808-813)
  • PromptInputTextarea (819-914)
  • PromptInputFooter (937-946)
  • PromptInputTools (950-955)
  • PromptInputSubmit (1025-1055)
src/components/ui/tooltip.tsx (1)
  • Tooltip (30-30)
src/components/ai-elements/reasoning.tsx (3)
  • Reasoning (44-112)
  • ReasoningTrigger (130-162)
  • ReasoningContent (170-183)
src/components/ai-elements/tool.tsx (5)
  • Tool (27-32)
  • ToolHeader (71-100)
  • ToolContent (104-112)
  • ToolInput (118-127)
  • ToolOutput (134-172)
src/components/ai-elements/message.tsx (3)
  • MessageResponse (309-320)
  • Message (30-39)
  • MessageContent (43-59)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/card.tsx (1)
  • Card (77-77)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ai-elements/conversation.tsx (4)
  • Conversation (12-20)
  • ConversationContent (26-34)
  • ConversationEmptyState (42-69)
  • ConversationScrollButton (73-100)
src/components/ai-elements/loader.tsx (1)
  • Loader (86-96)
src/components/ai-elements/suggestion.tsx (2)
  • Suggestions (9-14)
  • Suggestion (24-43)
src/components/ai-elements/model-selector.tsx (3)
src/components/ui/dialog.tsx (4)
  • Dialog (110-110)
  • DialogTrigger (113-113)
  • DialogContent (115-115)
  • DialogTitle (118-118)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/command.tsx (9)
  • Command (144-144)
  • CommandDialog (145-145)
  • CommandInput (146-146)
  • CommandList (147-147)
  • CommandEmpty (148-148)
  • CommandGroup (149-149)
  • CommandItem (150-150)
  • CommandShortcut (151-151)
  • CommandSeparator (152-152)
src/components/ui/textarea.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/reasoning.tsx (3)
src/components/ui/collapsible.tsx (3)
  • Collapsible (9-9)
  • CollapsibleTrigger (9-9)
  • CollapsibleContent (9-9)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/shimmer.tsx (1)
  • Shimmer (64-64)
src/components/ai-elements/controls.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/command.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/dialog.tsx (2)
  • Dialog (110-110)
  • DialogContent (115-115)
src/components/ai-elements/queue.tsx (4)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/scroll-area.tsx (1)
  • ScrollArea (48-48)
src/components/ui/collapsible.tsx (3)
  • Collapsible (9-9)
  • CollapsibleTrigger (9-9)
  • CollapsibleContent (9-9)
src/components/ai-elements/tool.tsx (3)
src/components/ui/collapsible.tsx (3)
  • Collapsible (9-9)
  • CollapsibleTrigger (9-9)
  • CollapsibleContent (9-9)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/code-block.tsx (1)
  • CodeBlock (75-130)
src/components/ai-elements/context.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/prompt-input.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
🪛 ast-grep (0.40.0)
src/components/ai-elements/code-block.tsx

[warning] 113-113: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)


[warning] 118-118: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 dotenv-linter (4.0.0)
.env

[warning] 17-17: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Redirect rules
  • GitHub Check: Header rules
  • GitHub Check: Pages changed

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (4)
app/api/chat/route.ts (1)

105-143: Note: MCP client resource leak previously flagged.

A past review comment already identified that mcpClient is not closed on the success path, which may leave connections open indefinitely. Please address the previous comment's suggestion to add cleanup in an onFinish callback or similar mechanism.

src/components/ai-elements/code-block.tsx (1)

87-99: Note: Prop change update bug previously flagged.

A past review comment already identified that the mounted.current pattern prevents re-highlighting when code, language, or showLineNumbers props change. Please address the previous suggestion to use a cancellation flag instead.

src/pages/config/details/ConfigDetailsPage.tsx (1)

151-163: Add null checks before accessing nested properties.

This issue was previously flagged: accessing json.configs[0].type without verifying that configs exists and has at least one element will throw a runtime error if the API returns an unexpected response structure.

Proposed fix
       const json = (await response.json()) as unknown;
-      switch ((json as Record<string, any>).configs[0].type) {
+      const configs = (json as Record<string, any>)?.configs;
+      const configType = configs?.[0]?.type;
+      switch (configType) {
         case "Kubernetes::Pod":
src/components/ai-elements/prompt-input.tsx (1)

1130-1187: Speech recognition recreated on every render when callbacks are unstable.

This issue was previously identified: the useEffect dependencies include textareaRef and onTranscriptionChange. If onTranscriptionChange is defined inline in the parent, the speech recognition instance recreates on every render, stopping any active recording.

🧹 Nitpick comments (13)
.npmrc (1)

1-1: Document the rationale for legacy-peer-deps=true and plan for re-evaluation.

Setting legacy-peer-deps=true bypasses peer dependency resolution checks, which can mask genuine incompatibilities or version conflicts. While this is a pragmatic workaround for dependency conflicts introduced by the new AI SDK integrations, it should be:

  1. Documented — add a comment or entry in migration/upgrade notes explaining why this is needed (e.g., which dependencies have unmet peer dependency declarations).
  2. Re-evaluated — plan to remove or narrow the scope of this setting once the involved dependencies (Anthropic, OpenAI SDKs, etc.) have updated their peer dependency declarations or the project's dependency graph stabilizes.

Consider also checking if a more targeted approach (e.g., using --legacy-peer-deps only during initial setup) is feasible for CI/CD environments to maintain visibility of peer conflicts in the future.

Do you want me to help draft migration notes or documentation explaining the peer dependency situation and the plan to address it?

src/components/ui/button.tsx (1)

27-28: Clarify or differentiate the "icon-sm" variant.

The new icon-sm variant uses identical dimensions (h-9 w-9) as the existing icon variant. This creates unnecessary duplication without functional distinction. Either differentiate the size (e.g., h-8 w-8) or remove the duplicate.

🔎 Proposed fix to differentiate sizing
        icon: "h-9 w-9",
-       "icon-sm": "h-9 w-9"
+       "icon-sm": "h-8 w-8"
src/App.tsx (1)

304-310: Verify permission and feature flag for AI navigation.

The AI navigation item reuses features.topology and tables.database for access control, which appears inconsistent with other navigation items that have dedicated features/resources. Consider whether AI should have its own feature flag and permission table entry for proper access control.

Do you want me to help verify the appropriate feature flag and permission settings for the AI feature, or help add dedicated entries if needed?

app/api/chat/tools.ts (1)

11-12: Strengthen type safety for MCP tool types.

The McpTool and McpTools types use Record<string, any>, which bypasses type checking. Consider importing or defining more specific types from the MCP SDK to improve type safety and catch potential issues at compile time.

src/components/ai-elements/tool.tsx (1)

146-152: Review string output handling.

The code treats all string outputs as JSON (line 151), which may cause display issues if the output is plain text rather than a JSON string. Consider checking if the string is valid JSON before using language="json", or defaulting to language="text" for non-JSON strings.

🔎 Proposed improvement
  if (typeof output === "object" && !isValidElement(output)) {
    Output = (
      <CodeBlock code={JSON.stringify(output, null, 2)} language="json" />
    );
  } else if (typeof output === "string") {
-   Output = <CodeBlock code={output} language="json" />;
+   // Try to parse as JSON, otherwise treat as plain text
+   try {
+     JSON.parse(output);
+     Output = <CodeBlock code={output} language="json" />;
+   } catch {
+     Output = <CodeBlock code={output} language="text" />;
+   }
  }
src/components/ai-elements/message.tsx (3)

191-198: Potential infinite re-render loop due to reference equality check.

The childrenArray is recreated on every render, so comparing branches.length !== childrenArray.length won't prevent unnecessary setBranches calls when the actual content hasn't changed—it only checks array length. More critically, if the length matches but references differ, the effect won't update; if it updates, the next render creates a new array, potentially causing stale or inconsistent state.

Consider memoizing the children array or using a more stable comparison:

Proposed fix
 export const MessageBranchContent = ({
   children,
   ...props
 }: MessageBranchContentProps) => {
   const { currentBranch, setBranches, branches } = useMessageBranch();
-  const childrenArray = Array.isArray(children) ? children : [children];
+  const childrenArray = useMemo(
+    () => (Array.isArray(children) ? children : [children]),
+    [children]
+  );

   // Use useEffect to update branches when they change
   useEffect(() => {
-    if (branches.length !== childrenArray.length) {
+    if (branches !== childrenArray) {
       setBranches(childrenArray);
     }
   }, [childrenArray, branches, setBranches]);

You'll need to import useMemo from React.


200-211: React key may be null or undefined if children lack explicit keys.

Using branch.key assumes all children have explicit keys. If they don't, React will log warnings and may have issues with reconciliation. Consider using the index as a fallback:

Proposed fix
   return childrenArray.map((branch, index) => (
     <div
       className={cn(
         "grid gap-2 overflow-hidden [&>div]:pb-0",
         index === currentBranch ? "block" : "hidden"
       )}
-      key={branch.key}
+      key={branch.key ?? index}
       {...props}
     >
       {branch}
     </div>
   ));

377-386: Missing TooltipProvider wrapper for file attachment tooltip.

The MessageAction component (lines 95-102) wraps its Tooltip with TooltipProvider, but here the Tooltip is used directly. This inconsistency could cause the tooltip to not function if there's no ancestor provider.

Proposed fix
       <>
+        <TooltipProvider>
           <Tooltip>
             <TooltipTrigger asChild>
               <div className="flex size-full shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
                 <PaperclipIcon className="size-4" />
               </div>
             </TooltipTrigger>
             <TooltipContent>
               <p>{attachmentLabel}</p>
             </TooltipContent>
           </Tooltip>
+        </TooltipProvider>
src/components/ai-elements/reasoning.tsx (2)

69-78: Duration calculation may have edge cases with rapid start/stop.

If isStreaming toggles rapidly (e.g., due to network issues), the effect could set startTime to null on the else branch while a new streaming session expects it to be set. This is a minor edge case but worth noting.

Alternative approach using refs for more reliable timing
+  const startTimeRef = useRef<number | null>(null);
+
   // Track duration when streaming starts and ends
   useEffect(() => {
     if (isStreaming) {
-      if (startTime === null) {
-        setStartTime(Date.now());
+      if (startTimeRef.current === null) {
+        startTimeRef.current = Date.now();
       }
-    } else if (startTime !== null) {
-      setDuration(Math.ceil((Date.now() - startTime) / MS_IN_S));
-      setStartTime(null);
+    } else if (startTimeRef.current !== null) {
+      setDuration(Math.ceil((Date.now() - startTimeRef.current) / MS_IN_S));
+      startTimeRef.current = null;
     }
-  }, [isStreaming, startTime, setDuration]);
+  }, [isStreaming, setDuration]);

179-181: Props spreading may pass unintended attributes to Streamdown.

The {...props} spread passes all CollapsibleContent props (including any DOM attributes) to Streamdown, which may not expect them. Consider explicitly passing only the props Streamdown needs:

Proposed fix
     <CollapsibleContent
       className={cn(
         "mt-4 text-sm",
         "text-muted-foreground outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2",
         className
       )}
       {...props}
     >
-      <Streamdown {...props}>{children}</Streamdown>
+      <Streamdown>{children}</Streamdown>
     </CollapsibleContent>
src/components/ai-elements/prompt-input.tsx (3)

299-373: Redundant key prop on internal div.

The key={data.id} on line 307 is unnecessary here since the parent PromptInputAttachments component already handles keying via Fragment. This won't cause issues but is redundant.

🔎 Proposed fix
         <div
           className={cn(
             "group relative flex h-8 cursor-pointer select-none items-center gap-1.5 rounded-md border border-border px-1.5 text-sm font-medium transition-all hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
             className
           )}
-          key={data.id}
           {...props}
         >

710-771: Silent error swallowing may hide issues during submission.

The error handling in the Promise chain (lines 754-756, 764-766, 768-770) silently swallows errors with empty catch blocks. While the intent to preserve user input on error is good, consider at least logging errors for debugging purposes.

🔎 Proposed fix - add error logging
           if (result instanceof Promise) {
             result
               .then(() => {
                 clear();
                 if (usingProvider) {
                   controller.textInput.clear();
                 }
               })
-              .catch(() => {
+              .catch((error) => {
+                console.error("Submission failed:", error);
                 // Don't clear on error - user may want to retry
               });
           } else {
             // Sync function completed without throwing, clear attachments
             clear();
             if (usingProvider) {
               controller.textInput.clear();
             }
           }
-        } catch {
+        } catch (error) {
+          console.error("Submission failed:", error);
           // Don't clear on error - user may want to retry
         }
       })
-      .catch(() => {
+      .catch((error) => {
+        console.error("Failed to convert attachments:", error);
         // Don't clear on error - user may want to retry
       });

1139-1141: Hardcoded speech recognition language limits i18n support.

The language is hardcoded to "en-US". Consider exposing this as a prop for internationalization.

🔎 Proposed enhancement
 export type PromptInputSpeechButtonProps = ComponentProps<
   typeof PromptInputButton
 > & {
   textareaRef?: RefObject<HTMLTextAreaElement | null>;
   onTranscriptionChange?: (text: string) => void;
+  lang?: string;
 };

 export const PromptInputSpeechButton = ({
   className,
   textareaRef,
   onTranscriptionChange,
+  lang = "en-US",
   ...props
 }: PromptInputSpeechButtonProps) => {
   // ...
       speechRecognition.continuous = true;
       speechRecognition.interimResults = true;
-      speechRecognition.lang = "en-US";
+      speechRecognition.lang = lang;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae187a7 and d72c09d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (38)
  • .npmrc (1 hunks)
  • AGENTS.md (1 hunks)
  • app/api/chat/route.ts (1 hunks)
  • app/api/chat/tools.ts (1 hunks)
  • components.json (1 hunks)
  • next-env.d.ts (1 hunks)
  • package.json (7 hunks)
  • pages/_app.tsx (3 hunks)
  • pages/ai.tsx (1 hunks)
  • src/App.tsx (3 hunks)
  • src/components/Configs/ConfigDetailsTabs.tsx (3 hunks)
  • src/components/SearchSelect/index.tsx (0 hunks)
  • src/components/ai-elements/code-block.tsx (1 hunks)
  • src/components/ai-elements/confirmation.tsx (1 hunks)
  • src/components/ai-elements/conversation.tsx (1 hunks)
  • src/components/ai-elements/loader.tsx (1 hunks)
  • src/components/ai-elements/message.tsx (1 hunks)
  • src/components/ai-elements/prompt-input.tsx (1 hunks)
  • src/components/ai-elements/reasoning.tsx (1 hunks)
  • src/components/ai-elements/shimmer.tsx (1 hunks)
  • src/components/ai-elements/suggestion.tsx (1 hunks)
  • src/components/ai-elements/tool.tsx (1 hunks)
  • src/components/ai/AiChat.tsx (1 hunks)
  • src/components/ai/AiChatPopover.tsx (1 hunks)
  • src/components/ui/alert.tsx (1 hunks)
  • src/components/ui/button-group.tsx (1 hunks)
  • src/components/ui/button.tsx (1 hunks)
  • src/components/ui/command.tsx (1 hunks)
  • src/components/ui/dialog.tsx (1 hunks)
  • src/components/ui/hover-card.tsx (1 hunks)
  • src/components/ui/input-group.tsx (1 hunks)
  • src/components/ui/popover.tsx (1 hunks)
  • src/components/ui/select.tsx (1 hunks)
  • src/components/ui/textarea.tsx (1 hunks)
  • src/pages/ai/AiChatPage.tsx (1 hunks)
  • src/pages/config/details/ConfigDetailsPage.tsx (3 hunks)
  • src/ui/Layout/SearchLayout.tsx (3 hunks)
  • tsconfig.json (2 hunks)
💤 Files with no reviewable changes (1)
  • src/components/SearchSelect/index.tsx
🚧 Files skipped from review as they are similar to previous changes (16)
  • src/components/ui/textarea.tsx
  • src/components/ui/hover-card.tsx
  • next-env.d.ts
  • AGENTS.md
  • pages/_app.tsx
  • src/components/ai-elements/confirmation.tsx
  • pages/ai.tsx
  • src/components/ui/select.tsx
  • src/components/ai/AiChatPopover.tsx
  • src/components/ai-elements/shimmer.tsx
  • src/components/ai-elements/conversation.tsx
  • src/components/ui/alert.tsx
  • package.json
  • src/pages/ai/AiChatPage.tsx
  • src/components/ui/input-group.tsx
  • src/components/ai-elements/suggestion.tsx
🧰 Additional context used
🧬 Code graph analysis (12)
src/components/ui/popover.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/pages/config/details/ConfigDetailsPage.tsx (2)
src/components/ai/AiChatPopover.tsx (1)
  • useAiChatPopover (92-102)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ai-elements/reasoning.tsx (3)
src/components/ui/collapsible.tsx (3)
  • Collapsible (9-9)
  • CollapsibleTrigger (9-9)
  • CollapsibleContent (9-9)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/shimmer.tsx (1)
  • Shimmer (64-64)
app/api/chat/route.ts (1)
app/api/chat/tools.ts (1)
  • buildChatTools (71-79)
src/components/ui/button-group.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/separator.tsx (1)
  • Separator (29-29)
src/components/ai-elements/loader.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/ui/Layout/SearchLayout.tsx (2)
src/components/ai/AiChatPopover.tsx (3)
  • AiChatPopoverProvider (37-90)
  • useAiChatPopover (92-102)
  • AiChatPopover (110-151)
src/ui/Layout/Preference.tsx (1)
  • PreferencePopOver (53-67)
src/components/ai-elements/tool.tsx (3)
src/components/ui/collapsible.tsx (3)
  • Collapsible (9-9)
  • CollapsibleTrigger (9-9)
  • CollapsibleContent (9-9)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/code-block.tsx (1)
  • CodeBlock (75-130)
src/components/ui/dialog.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/code-block.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/App.tsx (3)
src/services/permissions/features.ts (1)
  • features (1-27)
src/context/UserAccessContext/permissions.ts (1)
  • tables (1-27)
src/pages/ai/AiChatPage.tsx (1)
  • AiChatPage (6-14)
src/components/ai-elements/prompt-input.tsx (7)
src/components/ui/hover-card.tsx (3)
  • HoverCardTrigger (27-27)
  • HoverCard (27-27)
  • HoverCardContent (27-27)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/dropdown-menu.tsx (3)
  • DropdownMenuItem (187-187)
  • DropdownMenuTrigger (185-185)
  • DropdownMenuContent (186-186)
src/components/ui/input-group.tsx (4)
  • InputGroup (169-169)
  • InputGroupTextarea (174-174)
  • InputGroupAddon (170-170)
  • InputGroupButton (171-171)
src/components/ui/select.tsx (5)
  • Select (147-147)
  • SelectTrigger (150-150)
  • SelectContent (151-151)
  • SelectItem (153-153)
  • SelectValue (149-149)
src/components/ui/command.tsx (7)
  • Command (144-144)
  • CommandInput (146-146)
  • CommandList (147-147)
  • CommandEmpty (148-148)
  • CommandGroup (149-149)
  • CommandItem (150-150)
  • CommandSeparator (152-152)
🪛 ast-grep (0.40.0)
src/components/ai-elements/code-block.tsx

[warning] 113-113: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)


[warning] 118-118: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🔇 Additional comments (32)
tsconfig.json (3)

42-47: Include paths updated for Next.js App Router.

The additions are appropriate:

  • "pages" — Next.js Pages Router directory
  • "app" — Next.js App Router directory
  • ".next/types/**/*.ts" — Next.js generated type definitions

Having both pages and app directories included means both routing systems will be active. This is valid for incremental migration but worth being aware of for routing conflicts.


4-8: Formatting-only changes — no concerns.

The lib and paths arrays have been reformatted to multi-line style without any functional changes.

Also applies to: 32-35


36-40: Next.js TypeScript plugin addition looks good.

This enables enhanced type checking for Next.js features like route parameters, next/link, and next/image. The next package (v14.2.2) is properly installed as a dependency, and the tsconfig configuration is correctly set up for Next.js App Router.

src/components/ai-elements/loader.tsx (3)

8-80: LGTM! Clean SVG spinner implementation.

The LoaderIcon component is well-implemented with:

  • Accessible <title> element for screen readers
  • currentColor for flexible theming
  • Configurable size with sensible default
  • Opacity variations that create an effective spinner visual

1-6: LGTM! Well-structured type definitions.

The imports and type definitions are appropriate:

  • Proper extension of HTMLAttributes<HTMLDivElement> allows full prop spreading
  • LoaderIconProps is correctly scoped as internal
  • Exported LoaderProps provides clear API contract

Also applies to: 82-84


86-96: Implementation is clean; accessibility concern already documented above.

The component structure is sound with proper:

  • Prop destructuring and spreading
  • className merging via cn utility
  • Size forwarding to LoaderIcon

Note: The accessibility concern regarding missing ARIA attributes has already been raised in the past review comment above and remains relevant.

app/api/chat/tools.ts (1)

61-67: LGTM! Well-structured tool implementation.

The plot_timeseries tool is well-designed with proper input/output schemas, validation, and a clear execution path.

src/components/ui/popover.tsx (1)

1-31: LGTM! Clean Radix UI wrapper.

The popover component follows established patterns for wrapping Radix UI primitives, with sensible defaults and proper styling integration.

src/components/ui/button-group.tsx (1)

1-87: LGTM! Well-structured button group implementation.

The button group components are cleanly implemented with proper variant handling, orientation support, and consistent prop forwarding patterns.

src/components/ai-elements/code-block.tsx (2)

113-119: XSS risk mitigated by trusted Shiki output.

The static analysis warning about dangerouslySetInnerHTML is a false positive here. The HTML content is generated by the Shiki syntax highlighter, which produces trusted, pre-sanitized HTML output. This is the standard pattern for rendering syntax-highlighted code with Shiki.


149-163: LGTM! Robust clipboard handling.

The copy-to-clipboard implementation correctly handles edge cases including missing Clipboard API, errors, and timeout-based state reset.

src/components/ai-elements/tool.tsx (1)

42-69: LGTM! Comprehensive state handling.

The status badge implementation covers all possible tool states with appropriate icons and labels, providing clear visual feedback for each phase of tool execution.

app/api/chat/route.ts (1)

82-92: The default model identifiers are valid: claude-haiku-4-5 is a valid Anthropic model, and gpt-4.1 is a valid OpenAI model. No changes needed.

Likely an incorrect or invalid review comment.

src/App.tsx (1)

620-622: AI route lacks authorization check applied to other feature routes.

The <AiChatPage /> index route does not use the withAuthorizationAccessCheck wrapper that is consistently applied to equivalent index routes in health, playbooks, applications, incidents, and catalog features. Verify whether this is intentional (public access by design) or if authorization should be added for consistency.

src/components/Configs/ConfigDetailsTabs.tsx (1)

29-29: LGTM!

Clean prop threading to support the new extra slot in the layout. The optional prop with proper forwarding to SearchLayout is well-implemented.

Also applies to: 38-39, 73-73

src/ui/Layout/SearchLayout.tsx (1)

41-95: LGTM!

Clean integration of the AI chat popover into the layout. The AiChatPopoverProvider is correctly placed at the root level to provide context to all descendants, and the trigger component has good accessibility attributes (aria-expanded, sr-only label, title).

src/components/ai/AiChat.tsx (2)

229-231: Reasoning content is only rendered during streaming.

The check if (reasoningPart.state !== "streaming") return null means users can only see reasoning while it's actively streaming—once complete, it disappears. Is this intentional? If users should be able to review the AI's reasoning after completion, consider removing or relaxing this condition.


81-104: Well-implemented type guard for timeseries output validation.

The isPlotTimeseriesOutput function properly validates the structure including checking the array contents. Good defensive coding practice.

src/components/ai-elements/reasoning.tsx (1)

44-112: Well-structured reasoning component with good UX patterns.

The auto-close behavior with delay, duration tracking, and controllable state pattern are well-implemented. The use of hasAutoClosed guard ensures the auto-close only happens once per lifecycle.

components.json (1)

21-23: Registry configuration is correct and approved.

The @ai-elements registry pointing to https://registry.ai-sdk.dev/{name}.json is the official Vercel AI Elements registry. This is a legitimate, well-maintained component library from Vercel for building AI-native applications. No concerns.

src/pages/config/details/ConfigDetailsPage.tsx (2)

8-8: The import path for toastError is correct. The alias @flanksource-ui/components/Toast/toast properly resolves to src/components/Toast/toast.ts where the function is exported.

Likely an incorrect or invalid review comment.


6-6: The Button import and usage are correct. The Button component at @flanksource-ui/components/ui/button is a standard shadcn-style component that supports variant="outline" and size="sm" props, as well as children content. The usage at lines 192-203 correctly implements this API.

src/components/ui/dialog.tsx (3)

1-28: LGTM - Clean Dialog primitive implementation.

Standard shadcn/ui pattern with proper forwardRef usage and displayName assignments. The overlay animation classes are correctly applied.


30-52: LGTM - DialogContent with proper accessibility.

The close button includes sr-only text for screen readers, and the component correctly composes Portal, Overlay, and Content. Animation classes handle open/closed states appropriately.


54-120: LGTM - Supporting components and exports.

DialogHeader, DialogFooter, DialogTitle, and DialogDescription are well-structured with sensible default styling. All exports are properly organized.

src/components/ui/command.tsx (3)

1-24: LGTM - Command base component.

Proper forwardRef usage with cmdk primitives. The "use client" directive is appropriate for this interactive component.


26-36: LGTM - CommandDialog composition.

Correctly combines Dialog with Command, applying appropriate styling overrides via CSS selectors for cmdk internal elements.


38-153: LGTM - Remaining Command primitives.

All components follow consistent patterns with proper ref forwarding, displayName assignments, and className merging. CommandItem includes appropriate data-attribute styling for disabled and selected states.

src/components/ai-elements/prompt-input.tsx (4)

73-117: LGTM - Well-structured context types and hooks.

The context types are clear and the hooks properly throw helpful error messages when used outside their required provider.


144-258: LGTM - Provider with proper blob URL lifecycle management.

Good use of refs to avoid stale closures in cleanup. The unmount cleanup at lines 204-212 correctly uses attachmentsRef.current to ensure all blob URLs are revoked.


829-863: LGTM - Good keyboard handling with IME support.

The composition event handling (lines 905-906) properly prevents Enter from submitting during IME input. The check for disabled submit button (lines 840-846) is a nice defensive pattern.


1217-1413: LGTM - Consistent wrapper components.

These thin wrappers maintain consistent API patterns and allow for future customization while delegating to the underlying primitives.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/pages/config/details/ConfigDetailsPage.tsx (1)

172-179: Minor: .filter(Boolean) is unnecessary here.

All elements in the array are non-empty strings, so the filter has no effect.

Proposed simplification
       const text = [
         "Config details and its related configs:",
         "```json",
         jsonText,
         "```"
       ]
-        .filter(Boolean)
         .join("\n\n");
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d72c09d and 9abe541.

📒 Files selected for processing (4)
  • app/api/chat/route.ts (1 hunks)
  • src/components/ai-elements/code-block.tsx (1 hunks)
  • src/components/ai/AiChat.tsx (1 hunks)
  • src/pages/config/details/ConfigDetailsPage.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/api/chat/route.ts
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-12-20T08:54:22.331Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ui/progress.tsx:0-0
Timestamp: 2025-12-20T08:54:22.331Z
Learning: The project uses shadcn/ui components. These components are kept in their original form without modifications, even if potential improvements are identified. This helps maintain consistency with upstream and simplifies future upgrades.

Applied to files:

  • src/components/ai/AiChat.tsx
  • src/components/ai-elements/code-block.tsx
📚 Learning: 2025-12-20T08:56:14.015Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ai-elements/prompt-input.tsx:1130-1187
Timestamp: 2025-12-20T08:56:14.015Z
Learning: AI SDK components from the registry (ai-elements: https://registry.ai-sdk.dev/) are kept in their original form without modifications, similar to shadcn/ui components. This helps maintain consistency with upstream and simplifies future upgrades.

Applied to files:

  • src/components/ai/AiChat.tsx
📚 Learning: 2025-12-20T08:56:14.015Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ai-elements/prompt-input.tsx:1130-1187
Timestamp: 2025-12-20T08:56:14.015Z
Learning: Do not modify AI SDK components sourced from the registry (e.g. ai-elements) to keep upstream compatibility and simplify upgrades. Apply this rule across all TSX files under src/components/ai-elements; if you must make an exception, document the reason and prefer non-mutating approaches (wrapping, composition) over direct edits.

Applied to files:

  • src/components/ai-elements/code-block.tsx
🧬 Code graph analysis (1)
src/components/ai-elements/code-block.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
🪛 ast-grep (0.40.0)
src/components/ai-elements/code-block.tsx

[warning] 111-111: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)


[warning] 116-116: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🔇 Additional comments (21)
src/pages/config/details/ConfigDetailsPage.tsx (4)

85-87: Clean integration of the AI button into the existing tab structure.

The extra prop pattern provides a flexible way to inject additional UI into the tabs component without modifying its core structure.


140-148: Error handling approach is reasonable.

The fallback from JSON parsing to a generic status message is appropriate. The as any cast on line 147 is a pragmatic choice given the unknown error shape.


151-168: Previous review feedback addressed: null checks now in place.

The optional chaining (?.) on line 154 properly guards against undefined configs or missing array elements.


197-208: Well-structured button with appropriate disabled state handling.

The button correctly disables during loading states and when configId is unavailable, with clear visual feedback via the "Sending..." text.

src/components/ai-elements/code-block.tsx (1)

1-176: AI SDK component from registry – implementation approved.

AI SDK components from the registry are kept in their original form to maintain consistency with upstream and simplify future upgrades.

The static analysis tool flags dangerouslySetInnerHTML usage (lines 111, 116) as a potential XSS risk. This is a false positive: Shiki uses hast (Abstract Syntax Tree) for generating HTML output, opening up the possibility for transformers to manipulate the intermediate HAST. This architecture ensures proper text node handling during serialization. The approach is widely adopted in production by Nuxt Content, VitePress, Astro, Vercel's Next.js docs, and other major frameworks, confirming its safety.

The implementation is correct, including the cancelled flag pattern in the useEffect (lines 85-97) that prevents state updates after unmount while properly re-highlighting when props change.

src/components/ai/AiChat.tsx (16)

1-59: Imports are well-organized and appropriate.

The imports are logically grouped by source and all appear to be used within the component.


61-66: Type definitions look good.

The discriminated union pattern with kind: "plot_timeseries" enables type-safe handling of different output types.


68-91: Robust type guard implementation.

The validation is thorough—checking the discriminant, array structure, and each element's shape. This defensive approach is appropriate for handling untrusted AI tool outputs.


93-126: Clean chart implementation with proper immutability.

Good practice spreading the array before sorting to avoid mutating props. The chart configuration is sensible.

One minor observation: toLocaleString() can produce lengthy labels depending on locale. If X-axis labels become crowded with many data points, consider a shorter format (e.g., time-only for same-day data). This is fine for now given minTickGap={16} provides some protection.


128-135: Props interface is well-designed.

Clear separation of required (chat) vs optional props. The callback optionality allows the component to be used in different contexts (standalone page vs popover).


145-155: Hook usage looks correct.

All destructured values from useChat are utilized within the component.


157-172: Tool approval flow is well-structured.

The logic correctly determines when a follow-up message is needed after approval. The status check ensures we don't interrupt an ongoing stream.

Consider whether errors from sendMessage() should surface to the user (e.g., via toast), though the AI SDK may handle this internally.


174-187: Submit handler correctly validates input.

Properly handles edge cases: empty text, whitespace-only text, and file-only submissions.


189-204: Callback implementations are correct.

Both handlers properly validate inputs and have correct dependency arrays.


206-230: Verify: Reasoning only visible during streaming.

The condition on lines 216-218 returns null when reasoningPart.state !== "streaming", meaning reasoning content disappears once streaming completes.

Is this intentional? If users should be able to review the AI's reasoning after the response is complete, this logic would need adjustment. If the intent is to only show reasoning as a "thinking indicator" during generation, then this is correct.


232-244: Clean extensibility pattern for custom tool outputs.

Easy to extend with additional tool-specific renderers while maintaining type safety via the guard.


246-320: Tool part rendering is comprehensive and well-structured.

Good defensive programming with the disabled={!approvalId} guards on confirmation buttons, and the state-based visibility logic for the confirmation UI is correct.


322-354: Message rendering logic is clean and follows React patterns.

Filtering out file parts and using composite keys for list items is appropriate.


356-384: Header controls are well-implemented.

Good accessibility with aria-label attributes. Conditional rendering based on optional callbacks provides flexibility for different integration contexts.


386-409: Conversation area handles all states appropriately.

Empty state, message list, and loading indicator are all well-handled. The pt-10 padding correctly accounts for the absolutely-positioned header controls.


411-451: Input area is well-structured.

Quick prompts are correctly disabled during processing. The stop button appearing only during streaming (not submitted) is reasonable since cancellation is most meaningful once token generation has begun.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/ui/Layout/SearchLayoutAiChat.tsx (1)

56-58: Document or enforce context requirement for AiChatButton.

AiChatButton renders AiChatPopoverWithTrigger, which calls useAiChatPopover() at line 14. This hook requires the component to be wrapped in AiChatPopoverProvider, but this requirement is not documented or enforced. Users may encounter runtime errors if they use AiChatButton standalone.

Consider either:

  1. Documenting that AiChatButton must be used within AiChatPopoverProvider
  2. Having AiChatButton wrap itself with the provider (like AiChatSection does)
Option 2: Self-contained AiChatButton
 export function AiChatButton({ shouldAutoOpen }: { shouldAutoOpen?: boolean }) {
-  return <AiChatPopoverWithTrigger shouldAutoOpen={shouldAutoOpen} />;
+  return (
+    <AiChatPopoverProvider>
+      <AiChatPopoverWithTrigger shouldAutoOpen={shouldAutoOpen} />
+    </AiChatPopoverProvider>
+  );
 }
src/ui/Layout/SearchLayout.tsx (2)

50-50: Consider resetting shouldAutoOpen after AI chat opens.

The shouldAutoOpen state is set to true when the AI button is clicked (line 55) but is never reset to false. While the auto-open logic in SearchLayoutAiChat.tsx (line 17) checks !open before triggering, the persistent true state may cause unexpected behavior if the popover state is reset or if multiple instances share this flag.

Consider resetting shouldAutoOpen after the AI chat has opened, or using a ref/callback pattern instead of persistent state:

 function SearchLayoutInner({
   ...
 }: IProps) {
   const [topologyCardSize, setTopologyCardSize] = useAtom(cardPreferenceAtom);
   const { requestAiFeatures, aiLoaded } = useAiFeatureLoader();
-  const [shouldAutoOpen, setShouldAutoOpen] = useState(false);
+  const shouldAutoOpenRef = useRef(false);

   const handleAiButtonClick = () => {
     if (!aiLoaded) {
       requestAiFeatures();
-      setShouldAutoOpen(true);
+      shouldAutoOpenRef.current = true;
     }
   };
   
   ...
   
-  <LazyAiChatButton shouldAutoOpen={shouldAutoOpen} />
+  <LazyAiChatButton shouldAutoOpen={shouldAutoOpenRef.current} />

Also applies to: 52-57, 97-97


139-140: Clarify comment about SendToAiButton.

The comment mentions "SendToAiButton can access context," but this component is not visible in the current file. This suggests other components in the codebase depend on this AI provider wrapper. Consider updating the comment to be more specific about which components require this context, or remove the specific component reference if it's not essential.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9abe541 and dea8750.

📒 Files selected for processing (6)
  • src/App.tsx (1 hunks)
  • src/pages/config/details/ConfigDetailsPage.tsx (2 hunks)
  • src/pages/config/details/SendToAiButton.tsx (1 hunks)
  • src/ui/Layout/AiFeatureLoader.tsx (1 hunks)
  • src/ui/Layout/SearchLayout.tsx (6 hunks)
  • src/ui/Layout/SearchLayoutAiChat.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-20T08:56:14.015Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ai-elements/prompt-input.tsx:1130-1187
Timestamp: 2025-12-20T08:56:14.015Z
Learning: AI SDK components from the registry (ai-elements: https://registry.ai-sdk.dev/) are kept in their original form without modifications, similar to shadcn/ui components. This helps maintain consistency with upstream and simplifies future upgrades.

Applied to files:

  • src/ui/Layout/SearchLayout.tsx
🧬 Code graph analysis (4)
src/pages/config/details/SendToAiButton.tsx (2)
src/components/ai/AiChatPopover.tsx (1)
  • useAiChatPopover (92-102)
src/components/Toast/toast.ts (1)
  • toastError (15-25)
src/ui/Layout/SearchLayoutAiChat.tsx (1)
src/components/ai/AiChatPopover.tsx (3)
  • useAiChatPopover (92-102)
  • AiChatPopover (110-151)
  • AiChatPopoverProvider (37-90)
src/pages/config/details/ConfigDetailsPage.tsx (2)
src/ui/Layout/AiFeatureLoader.tsx (1)
  • AiFeatureRequest (41-49)
src/ui/Buttons/Button.tsx (1)
  • Button (26-101)
src/ui/Layout/SearchLayout.tsx (1)
src/ui/Layout/AiFeatureLoader.tsx (2)
  • useAiFeatureLoader (31-39)
  • AiFeatureLoaderProvider (17-29)
🪛 GitHub Check: eslint
src/App.tsx

[warning] 20-20:
'Sparkles' is defined but never used

🔇 Additional comments (8)
src/pages/config/details/ConfigDetailsPage.tsx (2)

19-24: Lazy loading implementation is correct.

The lazy loading pattern properly defers the AI SDK bundle until the button is actually rendered, improving initial page load performance.


98-113: Well-structured AI feature integration.

The combination of AiFeatureRequest, Suspense, and lazy loading ensures the AI features are loaded on-demand with appropriate loading states. The fallback button with a pulsating Sparkles icon provides good user feedback during the lazy load.

src/pages/config/details/SendToAiButton.tsx (5)

49-64: Null checks properly implemented.

The optional chaining at line 50 ((json as any)?.configs?.[0]?.type) safely extracts the config type without risking runtime errors. This correctly addresses the past review comment about accessing nested properties.


66-75: Grammar corrected and defensive JSON handling.

Line 69 correctly uses "its" (possessive) instead of "it's" (contraction), addressing the past review comment. The check at line 67 for whether json is already a string is a good defensive practice.


29-45: Robust error handling with graceful fallback.

The error path correctly attempts to parse a JSON error response and falls back to a default message when parsing fails. The finally block at lines 88-90 ensures isSending is reset even when errors occur, preventing the button from getting stuck in a disabled state.


30-34: Secure URL construction.

Using encodeURIComponent for the configId parameter properly prevents injection vulnerabilities.


12-16: Excellent state management and race condition prevention.

The isSending state combined with the computed disabled flag prevents race conditions from multiple button clicks. The finally block ensures cleanup regardless of success or failure, and the useCallback dependencies are correctly specified.

Also applies to: 22-22, 86-91

src/ui/Layout/SearchLayout.tsx (1)

16-27: LGTM: Effective lazy loading pattern for AI features.

The lazy loading implementation successfully defers the AI SDK bundle until first use, with clear loading states and proper Suspense boundaries. The on-demand loading via requestAiFeatures provides good UX by only loading AI features when users interact with the AI button.

Also applies to: 85-109, 140-148

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
package.json (1)

11-14: Beta dependencies with caret ranges risk unexpected breakage.

These AI SDK packages use beta versions with caret (^) ranges, allowing automatic updates to newer beta versions that may contain breaking changes. A previous review flagged this concern. Consider pinning to exact versions (remove ^) until stable releases are available.

🧹 Nitpick comments (6)
src/components/ui/button-group.tsx (1)

41-62: Consider stricter discriminated union for type safety.

The component works correctly, but the type definition allows passing props for both div and Slot regardless of the asChild value. While this is a common pattern in polymorphic components and runtime behavior is correct, stricter typing would improve developer experience.

🔎 Optional: Stricter type definition using discriminated union
-function ButtonGroupText({
-  className,
-  asChild = false,
-  ...props
-}: (
-  | React.ComponentPropsWithoutRef<"div">
-  | React.ComponentPropsWithoutRef<typeof Slot>
-) & {
-  asChild?: boolean;
-}) {
+function ButtonGroupText({
+  className,
+  asChild = false,
+  ...props
+}: 
+  | ({ asChild?: false } & React.ComponentPropsWithoutRef<"div">)
+  | ({ asChild: true } & React.ComponentPropsWithoutRef<typeof Slot>)
+) {

This approach ensures that when asChild is true, only Slot props are allowed, and when false, only div props are allowed.

.npmrc (1)

1-1: Consider documenting why legacy-peer-deps is needed and plan for resolution.

This setting bypasses peer dependency validation, which can mask genuine compatibility issues between packages. While it may be necessary given the scale of new AI SDK dependencies, consider:

  1. Adding a comment explaining which specific conflicts required this workaround
  2. Creating a tracking issue to resolve the underlying peer dependency conflicts
app/api/chat/route.ts (2)

99-103: Consider validating message structure beyond array check.

The validation only checks if messages is an array. Malformed message objects (missing role, content, etc.) will cause errors deeper in the stack. Consider adding schema validation with Zod (already a dependency).

🔎 Example with Zod validation
import { z } from "zod";

const MessageSchema = z.object({
  role: z.enum(["user", "assistant", "system"]),
  content: z.string()
});

const RequestSchema = z.object({
  messages: z.array(MessageSchema).min(1)
});

// In POST handler:
const parseResult = RequestSchema.safeParse(await req.json());
if (!parseResult.success) {
  return new Response("Invalid request body", { status: 400 });
}
const { messages } = parseResult.data;

55-71: Consider adding timeout for external HTTP calls.

fetchLLMConnection makes an external HTTP request without a timeout. If the backend is slow or unresponsive, this could hang indefinitely. Consider adding an AbortController with a reasonable timeout.

🔎 Example with timeout
async function fetchLLMConnection(backendUrl: string, cookieHeader: string) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout
  
  try {
    const url = new URL("/connection/llm", backendUrl).toString();
    const response = await fetch(url, {
      headers: { Cookie: cookieHeader },
      signal: controller.signal,
      next: { revalidate: 5 * 60 }
    });
    // ... rest of implementation
  } finally {
    clearTimeout(timeoutId);
  }
}
src/components/ui/input-group.tsx (1)

73-78: Click-to-focus only targets input, but InputGroupTextarea is also supported.

The onClick handler queries only input elements, so clicking the addon won't focus a textarea.

🔎 Proposed fix
       onClick={(e) => {
         if ((e.target as HTMLElement).closest("button")) {
           return;
         }
-        e.currentTarget.parentElement?.querySelector("input")?.focus();
+        const parent = e.currentTarget.parentElement;
+        const focusable = parent?.querySelector("input") ?? parent?.querySelector("textarea");
+        focusable?.focus();
       }}
src/ui/Layout/SearchLayout.tsx (1)

49-57: shouldAutoOpen is never reset after the popover opens.

Once set to true, shouldAutoOpen remains true for the component's lifetime. This works correctly because AiChatPopoverWithTrigger only opens when shouldAutoOpen && !open, preventing re-opening after user closes. However, for clarity and to avoid potential edge cases, consider resetting it.

🔎 Proposed improvement

If you want to be explicit about the one-time auto-open behavior:

+  // Reset shouldAutoOpen after it's consumed to make intent explicit
+  useEffect(() => {
+    if (shouldAutoOpen && aiLoaded) {
+      setShouldAutoOpen(false);
+    }
+  }, [shouldAutoOpen, aiLoaded]);

Alternatively, document the current behavior with a comment.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dea8750 and 2feb2b0.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (39)
  • .npmrc
  • AGENTS.md
  • app/api/chat/route.ts
  • app/api/chat/tools.ts
  • components.json
  • next-env.d.ts
  • package.json
  • pages/_app.tsx
  • pages/ai.tsx
  • src/components/Configs/ConfigDetailsTabs.tsx
  • src/components/SearchSelect/index.tsx
  • src/components/ai-elements/code-block.tsx
  • src/components/ai-elements/confirmation.tsx
  • src/components/ai-elements/conversation.tsx
  • src/components/ai-elements/loader.tsx
  • src/components/ai-elements/message.tsx
  • src/components/ai-elements/prompt-input.tsx
  • src/components/ai-elements/reasoning.tsx
  • src/components/ai-elements/shimmer.tsx
  • src/components/ai-elements/suggestion.tsx
  • src/components/ai-elements/tool.tsx
  • src/components/ai/AiChat.tsx
  • src/components/ai/AiChatPopover.tsx
  • src/components/ui/alert.tsx
  • src/components/ui/button-group.tsx
  • src/components/ui/button.tsx
  • src/components/ui/command.tsx
  • src/components/ui/dialog.tsx
  • src/components/ui/hover-card.tsx
  • src/components/ui/input-group.tsx
  • src/components/ui/popover.tsx
  • src/components/ui/select.tsx
  • src/components/ui/textarea.tsx
  • src/pages/config/details/ConfigDetailsPage.tsx
  • src/pages/config/details/SendToAiButton.tsx
  • src/ui/Layout/AiFeatureLoader.tsx
  • src/ui/Layout/SearchLayout.tsx
  • src/ui/Layout/SearchLayoutAiChat.tsx
  • tsconfig.json
💤 Files with no reviewable changes (1)
  • src/components/SearchSelect/index.tsx
🚧 Files skipped from review as they are similar to previous changes (17)
  • src/components/ui/textarea.tsx
  • src/components/Configs/ConfigDetailsTabs.tsx
  • src/components/ui/hover-card.tsx
  • AGENTS.md
  • src/components/ui/alert.tsx
  • src/pages/config/details/ConfigDetailsPage.tsx
  • tsconfig.json
  • src/components/ui/popover.tsx
  • src/components/ui/select.tsx
  • src/components/ai-elements/shimmer.tsx
  • src/components/ai/AiChatPopover.tsx
  • app/api/chat/tools.ts
  • src/ui/Layout/AiFeatureLoader.tsx
  • pages/_app.tsx
  • src/components/ai-elements/loader.tsx
  • src/components/ui/button.tsx
  • pages/ai.tsx
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-20T08:54:22.331Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ui/progress.tsx:0-0
Timestamp: 2025-12-20T08:54:22.331Z
Learning: Do not modify shadcn/ui components themselves. Keep them in their original form to maintain upstream consistency and simplify upgrades. If you identify improvements, implement them via project-specific wrappers, composition, or separate utility components rather than editing the upstream component files.

Applied to files:

  • src/components/ui/button-group.tsx
  • src/components/ui/input-group.tsx
  • src/components/ui/command.tsx
  • src/components/ui/dialog.tsx
📚 Learning: 2025-12-20T08:56:14.015Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ai-elements/prompt-input.tsx:1130-1187
Timestamp: 2025-12-20T08:56:14.015Z
Learning: Do not modify AI SDK components sourced from the registry (e.g. ai-elements) to keep upstream compatibility and simplify upgrades. Apply this rule across all TSX files under src/components/ai-elements; if you must make an exception, document the reason and prefer non-mutating approaches (wrapping, composition) over direct edits.

Applied to files:

  • src/components/ai-elements/suggestion.tsx
  • src/components/ai-elements/confirmation.tsx
  • src/components/ai-elements/code-block.tsx
  • src/components/ai-elements/reasoning.tsx
  • src/components/ai-elements/tool.tsx
  • src/components/ai-elements/conversation.tsx
  • src/components/ai-elements/message.tsx
  • src/components/ai-elements/prompt-input.tsx
📚 Learning: 2025-12-20T08:56:21.836Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ai-elements/prompt-input.tsx:1130-1187
Timestamp: 2025-12-20T08:56:21.836Z
Learning: AI SDK components from the registry (ai-elements: https://registry.ai-sdk.dev/) are kept in their original form without modifications, similar to shadcn/ui components. This helps maintain consistency with upstream and simplifies future upgrades.

Applied to files:

  • components.json
  • src/components/ai/AiChat.tsx
  • src/ui/Layout/SearchLayout.tsx
📚 Learning: 2025-12-20T08:54:29.416Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ui/progress.tsx:0-0
Timestamp: 2025-12-20T08:54:29.416Z
Learning: The project uses shadcn/ui components. These components are kept in their original form without modifications, even if potential improvements are identified. This helps maintain consistency with upstream and simplifies future upgrades.

Applied to files:

  • src/components/ai-elements/code-block.tsx
  • src/components/ai/AiChat.tsx
  • src/components/ai-elements/prompt-input.tsx
🧬 Code graph analysis (12)
src/components/ui/button-group.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/separator.tsx (1)
  • Separator (29-29)
src/ui/Layout/SearchLayoutAiChat.tsx (1)
src/components/ai/AiChatPopover.tsx (3)
  • useAiChatPopover (92-102)
  • AiChatPopover (110-151)
  • AiChatPopoverProvider (37-90)
src/components/ui/input-group.tsx (4)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/input.tsx (1)
  • Input (22-22)
src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
src/components/ai-elements/suggestion.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ai-elements/confirmation.tsx (3)
src/components/ui/alert.tsx (2)
  • Alert (59-59)
  • AlertDescription (59-59)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/pages/config/details/SendToAiButton.tsx (2)
src/components/ai/AiChatPopover.tsx (1)
  • useAiChatPopover (92-102)
public/mockServiceWorker.js (1)
  • response (127-132)
src/components/ai-elements/code-block.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ai-elements/tool.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/ui/Layout/SearchLayout.tsx (1)
src/ui/Layout/AiFeatureLoader.tsx (2)
  • useAiFeatureLoader (32-40)
  • AiFeatureLoaderProvider (18-30)
src/components/ai-elements/conversation.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/dialog.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/message.tsx (4)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/tooltip.tsx (4)
  • TooltipProvider (30-30)
  • Tooltip (30-30)
  • TooltipTrigger (30-30)
  • TooltipContent (30-30)
src/components/ui/button-group.tsx (2)
  • ButtonGroup (83-83)
  • ButtonGroupText (85-85)
🪛 ast-grep (0.40.0)
src/components/ai-elements/code-block.tsx

[warning] 111-111: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)


[warning] 116-116: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Redirect rules
  • GitHub Check: Header rules
  • GitHub Check: Pages changed
🔇 Additional comments (40)
src/components/ui/button-group.tsx (5)

1-5: LGTM!

The imports are clean, organized, and all dependencies are appropriately used throughout the component.


7-22: LGTM!

The variant definition properly handles horizontal and vertical orientations with appropriate default styling. The complex Tailwind selectors are intentional for managing child element states, borders, and border radii in grouped layouts.


24-39: LGTM!

The ButtonGroup component is well-implemented with proper accessibility (role="group"), type-safe props, and appropriate data attributes for styling and component identification.


64-80: LGTM!

The separator component correctly wraps the base Separator with button-group-specific styling. The use of !m-0 (applying !important to margin) is intentional defensive styling to ensure the button group controls spacing consistently, which is a common pattern in design system components.


82-87: LGTM!

All components and the variants function are properly exported, providing a complete and flexible API for consumers.

next-env.d.ts (1)

3-3: Editing auto-generated file may cause maintenance issues.

This file is auto-generated by Next.js (as noted in line 5-6). Manual additions may be overwritten when Next.js regenerates this file during builds or version upgrades. If this reference is required, consider verifying whether Next.js 14.2.x should auto-include it, or document this manual addition so it can be restored if lost.

package.json (1)

167-168: LGTM on build memory allocation increase.

Increasing max-old-space-size to 8192MB is reasonable for builds with extensive dependencies and TypeScript compilation. The change is applied consistently to both build and build:clerk scripts.

src/components/ai-elements/suggestion.tsx (1)

1-43: LGTM! Clean component implementation.

The component follows good patterns:

  • Proper TypeScript types using ComponentProps for type inference
  • Correct cn() usage for className merging with overrides
  • Appropriate prop spreading with explicit prop extraction

Based on learnings, this appears to be an AI SDK registry component that should remain unmodified for upstream compatibility.

app/api/chat/route.ts (2)

126-141: MCP client lifecycle properly handled.

The onError and onFinish callbacks correctly close the MCP client, addressing the resource leak concern from the previous review. The optional chaining on mcpClient?.close() safely handles edge cases.


82-97: Both default model names are valid; focus on API key validation instead.

The model identifiers are correct: claude-haiku-4-5 is the current Claude API model identifier, and gpt-4.1 is a valid OpenAI model introduced in 2025.

However, the empty string fallback for missing API keys (connection.password ?? "") remains a valid concern. This pattern will defer the error until the provider attempts an API request rather than failing fast. Consider validating the API key presence earlier:

 function buildLLMModel(connection: LLMConnection): LanguageModelV3 {
+  if (!connection.password) {
+    throw new Error("Missing API key in LLM connection");
+  }
   const modelId = resolveModelId(connection);

Likely an incorrect or invalid review comment.

src/components/ui/input-group.tsx (3)

1-38: LGTM!

The InputGroup component is well-structured with comprehensive variant handling for alignment, focus, and error states using data attributes and CSS selectors.


102-119: LGTM!

The InputGroupButton correctly handles the size prop collision by omitting it from Button's props and redefining it with custom variants.


136-166: LGTM!

InputGroupInput and InputGroupTextarea properly wire the data-slot attribute for focus-visible styling and reset conflicting styles from base components.

src/components/ai-elements/code-block.tsx (3)

85-97: LGTM!

The effect correctly uses a cancelled flag to prevent state updates after unmount and properly re-highlights when code, language, or showLineNumbers props change.


109-118: Acknowledge XSS consideration with dangerouslySetInnerHTML.

The static analysis flagged this, but since shiki generates the HTML from the code prop (not arbitrary user HTML), this is acceptable as long as the code prop originates from trusted sources. Per project policy, this AI SDK registry component is kept in its original form.


136-176: LGTM!

The copy button correctly handles missing Clipboard API, provides proper callbacks for success/error, and manages the copied state with a configurable timeout.

src/components/ui/dialog.tsx (1)

1-120: LGTM!

Standard shadcn/ui Dialog component with proper Radix primitive composition, forwardRef usage, and accessibility attributes. Per project policy, keeping it in original form.

src/components/ai-elements/message.tsx (4)

26-39: LGTM!

The Message component correctly applies role-based styling with conditional classes for user vs assistant messages.


186-212: Note: Branch update logic and key handling.

The effect only compares branches.length vs childrenArray.length, so if children content changes without length changing, branches may not update. Also, branch.key could be undefined if children lack explicit keys. Per project policy, this AI SDK registry component is kept in its original form.

Ensure that when using MessageBranchContent, children elements have explicit key props for stable rendering.


309-322: LGTM!

MessageResponse is properly memoized with a custom equality function that compares children, optimizing re-renders for streaming content.


377-386: Note: Tooltip without TooltipProvider wrapper.

Unlike MessageAction (lines 95-102) which wraps tooltips in TooltipProvider, this Tooltip usage lacks the provider. This may cause issues if no ancestor provides the context. Per project policy, this AI SDK registry component is kept in its original form—consider wrapping at a higher level if tooltips don't appear.

components.json (1)

21-23: LGTM!

Registry configuration correctly added for AI SDK components, enabling component fetching via the shadcn CLI pattern.

src/ui/Layout/SearchLayoutAiChat.tsx (2)

9-37: LGTM!

The AiChatPopoverWithTrigger component correctly handles auto-open behavior via useEffect and passes the trigger to AiChatPopover.


56-58: AiChatButton requires AiChatPopoverProvider ancestor.

AiChatButton uses useAiChatPopover() internally, which throws if not within AiChatPopoverProvider. Unlike AiChatSection which wraps its content in the provider, AiChatButton does not. This is intentional for the current usage in SearchLayout.tsx (where it's wrapped by LazyAiChatProvider), but could cause runtime errors if used standalone.

Consider adding a JSDoc comment to document this requirement:

+/**
+ * Renders an AI chat trigger button. Must be used within an AiChatPopoverProvider.
+ */
 export function AiChatButton({ shouldAutoOpen }: { shouldAutoOpen?: boolean }) {
   return <AiChatPopoverWithTrigger shouldAutoOpen={shouldAutoOpen} />;
 }
src/ui/Layout/SearchLayout.tsx (3)

16-27: LGTM!

Lazy loading AI chat components via React.lazy with module renaming pattern is correct for code splitting and avoids bundling AI SDK until first use.


85-109: LGTM!

Good UX pattern with a pulsing Suspense fallback while the lazy component loads, and proper accessibility attributes on the pre-load button.


139-157: LGTM!

The conditional provider wrapping pattern correctly ensures SendToAiButton (and other AI-dependent components) can access the popover context only after AI features are loaded.

src/pages/config/details/SendToAiButton.tsx (1)

1-105: LGTM! Clean implementation with proper error handling and state management.

The component correctly:

  • Guards against missing configId before making requests
  • Encodes the URL parameter to prevent injection
  • Uses finally to ensure isSending state cleanup
  • Dynamically adds context-specific quick prompts based on config type

Minor observation: the any casts on lines 43, 50, and 87 could benefit from stricter typing, but this is acceptable for a toast utility that likely accepts various error types.

src/components/ai/AiChat.tsx (6)

61-91: Well-structured type guard with comprehensive validation.

The isPlotTimeseriesOutput function correctly:

  • Handles null/undefined inputs
  • Validates the discriminant kind property
  • Ensures timeseries is an array with proper element structure

93-126: LGTM! Chart component follows good practices.

The implementation correctly:

  • Avoids mutating output.timeseries by spreading before sorting
  • Uses human-readable labels for X-axis while sorting by actual Date objects
  • Disables animation for potentially streaming/updating data

157-172: LGTM! Tool approval handler with follow-up message logic.

The conditional follow-up message (isWaitingForFollowUp) correctly avoids sending during active streaming/submission states, preventing race conditions.


206-230: Reasoning part only renders during streaming - verify this is intentional.

The renderReasoningPart function returns null when state !== "streaming" (lines 216-218), meaning completed reasoning is never displayed. If the intent is to show reasoning history after streaming ends, this condition should be revisited.


246-320: Tool part rendering handles all states correctly.

The implementation properly:

  • Derives approval ID from part.approval?.id with fallback to toolCallId
  • Hides confirmation UI for terminal states (output-denied, output-available)
  • Renders custom output (chart) below the standard tool output when applicable

356-454: LGTM! Well-structured chat UI composition.

The main render correctly:

  • Conditionally renders New/Close buttons based on callback availability
  • Shows loading indicator only during streaming or submitted states
  • Disables suggestions during active streaming/submission
  • Provides stop functionality during streaming
src/components/ai-elements/reasoning.tsx (1)

1-187: LGTM! AI SDK registry component.

Based on learnings, this component is from the AI SDK registry (ai-elements) and should be kept in its original form to maintain upstream compatibility and simplify future upgrades. The implementation is well-structured with proper context management, timer cleanup, and streaming state tracking.

src/components/ai-elements/conversation.tsx (1)

1-100: LGTM! AI SDK registry component.

Based on learnings, this component is from the AI SDK registry and should be kept in its original form. The implementation correctly uses use-stick-to-bottom for auto-scrolling behavior, with proper context consumption and memoized handlers.

src/components/ai-elements/confirmation.tsx (1)

1-178: LGTM! AI SDK registry component.

Based on learnings, this component is from the AI SDK registry and should be kept in its original form to maintain upstream compatibility. The state-driven confirmation flow correctly handles various tool approval states with appropriate conditional rendering.

src/components/ai-elements/tool.tsx (1)

1-172: LGTM! AI SDK registry component is well-structured.

The Tool UI module provides a clean, composable interface for displaying AI tool states with appropriate visual feedback. The component correctly handles different tool states, input/output rendering, and error conditions.

Based on learnings, this AI SDK registry component is kept in its original form to maintain upstream compatibility and simplify future upgrades.

src/components/ui/command.tsx (1)

1-153: LGTM! shadcn/ui command component follows established patterns.

The command palette module correctly composes cmdk primitives with consistent styling, proper ref forwarding, and appropriate displayName assignments. The component provides a solid foundation for building command-driven interfaces.

Based on learnings, this shadcn/ui component is kept in its original form to maintain upstream consistency and simplify future upgrades.

src/components/ai-elements/prompt-input.tsx (1)

1-1413: LGTM! Comprehensive AI SDK registry component with solid architecture.

The PromptInput module provides a feature-rich, well-architected solution with:

  • Proper blob URL lifecycle management (creation on add, revocation on remove/clear/unmount)
  • Correct async blob-to-data URL conversion before submission
  • Well-designed dual-mode operation (provider-based or local state)
  • Appropriate file validation (accept patterns, maxFiles, maxFileSize)
  • Clean keyboard shortcuts and paste handling

Based on learnings, this AI SDK registry component is kept in its original form to maintain upstream compatibility and simplify future upgrades.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
package.json (1)

11-14: Verify current versions and consider pinning beta dependencies.

Based on the past review, there was a concern that these @ai-sdk/* packages are using beta v3 versions when stable v2 versions exist. Additionally, using caret ranges (^) with beta versions is risky as beta releases may introduce breaking changes in minor/patch versions.

Please verify the current stable versions and consider either:

  1. Upgrading to stable releases if they meet your requirements, or
  2. Pinning to exact beta versions (remove ^) to prevent unexpected breaking changes
#!/bin/bash
# Check latest versions of AI SDK packages on npm
for pkg in "@ai-sdk/anthropic" "@ai-sdk/openai" "@ai-sdk/react" "@ai-sdk/mcp"; do
  echo "=== $pkg ==="
  npm view "$pkg" versions --json | jq -r '.[-5:][]' 2>/dev/null || npm view "$pkg" version
  echo ""
done
🧹 Nitpick comments (2)
package.json (2)

166-167: Document memory requirements for builds.

The build scripts now allocate 8GB of heap memory for Node.js. This is a substantial increase and suggests the build process is memory-intensive, likely due to the expanded dependency surface (AI SDKs, UI libraries, etc.).

Ensure your CI/CD environments have sufficient memory (at least 10-12GB total) to accommodate this allocation. Consider documenting this requirement in your README or CI configuration comments.

If build memory usage continues to grow, investigate:

  • Code splitting strategies
  • Lazy loading more modules
  • Build output analysis to identify large dependencies
  • Potential webpack/Next.js optimization opportunities

23-25: Consider upgrading to Mantine v7 or v8, or verify v6 is intentional.

Mantine's latest version is 8.3.10, with v7.17 available as the latest v7 release. v6.0.21 is documented as the latest v6, though your package.json specifies v6.0.22. If v6 is intentional for this project, no action is needed. Otherwise, upgrading to v8 or v7 may provide significant improvements, though this would require migration effort.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2feb2b0 and 3964759.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • package.json
🔇 Additional comments (1)
package.json (1)

146-146: LGTM - Zod v4 added as new dependency.

Zod v4.2.1 is being introduced to the project with no migration concerns needed.

Copy link

@coderabbitai coderabbitai bot left a comment

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 (2)
app/api/chat/route.ts (2)

101-120: Consider failing fast on missing API key.

When connection.password is undefined, the code defaults to an empty string for apiKey. This will likely result in authentication errors from the provider, but with less clear error messages. Consider throwing early if the API key is missing.

🔎 Proposed improvement
 function buildLLMModel(connection: LLMConnection): LanguageModelV3 {
   const modelId = resolveModelId(connection);
+  
+  if (!connection.password) {
+    throw new Error(`Missing API key for ${connection.type} provider`);
+  }

   switch (connection.type) {
     case "anthropic": {
       const anthropicProvider = createAnthropic({
-        apiKey: connection.password ?? ""
+        apiKey: connection.password
       });
       return anthropicProvider(modelId ?? "claude-haiku-4-5");
     }
     case "openai": {
       const openaiProvider = createOpenAI({
-        apiKey: connection.password ?? ""
+        apiKey: connection.password
       });
       return openaiProvider(modelId ?? "gpt-4.1");
     }

191-197: Consider logging on all outcomes, not just errors.

The wideEvent object captures useful telemetry, but console.log is only called in finally. Since onFinish sets status: "completed", successful completions are logged via console.log. However, if the stream is still in progress when the response is returned (which is the case with streaming), the finally block runs with status: "started".

This is expected behavior for streaming responses where the handler returns before the stream completes. The actual completion logging happens in onFinish. Consider removing or clarifying the finally block logging since it will always log status: "started" for successful streaming responses.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81adf2f and 51af003.

📒 Files selected for processing (3)
  • app/api/chat/route.ts
  • app/api/chat/tools.ts
  • src/ui/Layout/SearchLayoutAiChat.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/ui/Layout/SearchLayoutAiChat.tsx
  • app/api/chat/tools.ts
🧰 Additional context used
🧬 Code graph analysis (1)
app/api/chat/route.ts (1)
app/api/chat/tools.ts (1)
  • buildChatTools (92-102)
🔇 Additional comments (4)
app/api/chat/route.ts (4)

52-59: LGTM!

The URL normalization logic correctly handles both trailing slashes on the base and leading slashes on the endpoint, ensuring proper URL construction.


61-94: LGTM!

Good defensive programming with content-type validation, JSON parsing error handling, and body snippet truncation for error messages. The 5-minute cache revalidation is appropriate for LLM configuration.


164-184: LGTM on streaming lifecycle management.

The onError and onFinish callbacks properly close the MCP client using optional chaining. The event metadata capture for observability is well-structured.


22-50: No issue with auth() usage. Clerk's auth() function is designed for server-side async contexts like Route Handlers. Calling it from within an async helper function (getBackendUrl()) that is invoked from the POST handler is correct and supported usage. Session claims are properly available in this Server Context.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

♻️ Duplicate comments (5)
package.json (3)

79-79: Verify if effect package is actually used.

The effect package (^3.19.13) appears to have no direct imports in the codebase based on prior analysis. If unused, consider removing it to reduce bundle size and dependency footprint.


64-65: Verify if Valibot packages are actually used.

Both @valibot/to-json-schema (^1.5.0) and valibot (^1.2.0) were flagged in prior review as having no usage in the codebase. The project uses zod for AI tool schemas. If these packages are unused, consider removing them.

Also applies to: 142-142


35-42: Radix UI version compatibility warrants verification.

The mix of @radix-ui/react-select v2 (^2.2.6) with v1 packages (hover-card ^1.1.15, popover ^1.1.15, etc.) may cause focus/dismissal issues when Select components are nested inside Popover content due to mismatched internal dependencies. This was flagged in a prior review.

public/mockServiceWorker.js (1)

4-11: MSW version mismatch — regenerate via CLI.

The service worker declares version 1.3.2, but package.json specifies MSW ^1.2.3. This auto-generated file should not be manually edited. Regenerate it with npx msw init public/ --save to align versions.

app/api/chat/route.ts (1)

151-162: MCP client leak if buildChatTools throws.

If createMCPClient succeeds but buildChatTools throws, the mcpClient won't be closed because the error is caught by the outer catch block, but onError/onFinish callbacks are never invoked (they're only called during streaming).

🔎 Proposed fix
     const mcpClient = await createMCPClient({
       transport: {
         type: "http",
         url: buildURL(backendUrl, "/mcp").toString(),
         headers: {
           Cookie: cookies
         }
       }
     });
-    const tools = await buildChatTools(mcpClient);
+    let tools;
+    try {
+      tools = await buildChatTools(mcpClient);
+    } catch (error) {
+      await mcpClient.close();
+      throw error;
+    }
🧹 Nitpick comments (6)
src/components/ui/input-group.tsx (1)

73-78: Consider also focusing textarea elements.

The click handler only focuses input elements, but InputGroup supports textareas (via InputGroupTextarea). Clicking on an addon when using a textarea won't focus it.

🔎 Proposed fix
       onClick={(e) => {
         if ((e.target as HTMLElement).closest("button")) {
           return;
         }
-        e.currentTarget.parentElement?.querySelector("input")?.focus();
+        const parent = e.currentTarget.parentElement;
+        (parent?.querySelector("input") ?? parent?.querySelector("textarea"))?.focus();
       }}
src/components/ai-elements/conversation.tsx (1)

83-99: Consider explicit null return for clarity.

The conditional rendering !isAtBottom && (...) returns false when at the bottom, which is valid React but could be more explicit.

🔎 Optional refactor for clarity
-  return (
-    !isAtBottom && (
-      <Button
+  if (isAtBottom) {
+    return null;
+  }
+
+  return (
+    <Button
        className={cn(
          "absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full",
          className
        )}
        onClick={handleScrollToBottom}
        size="icon"
        type="button"
        variant="outline"
        {...props}
      >
        <ArrowDownIcon className="size-4" />
      </Button>
-    )
-  );
+  );
app/api/chat/route.ts (1)

122-127: Consider utilizing wideEvent for structured observability.

The wideEvent object collects useful telemetry (messages count, backend URL, LLM details, usage, errors) but is only logged to console. Consider integrating with a structured logging or observability system to maximize its value for debugging and monitoring in production.

src/ui/Layout/SearchLayout.tsx (2)

17-21: Consider renaming for clarity.

The variable LazyAiChatProvider imports AiChatSection, creating a naming mismatch. Consider renaming to LazyAiChatSection to match the actual component being imported.

🔎 Suggested naming fix
-const LazyAiChatProvider = lazy(() =>
+const LazyAiChatSection = lazy(() =>
   import("./SearchLayoutAiChat").then((module) => ({
     default: module.AiChatSection
   }))
 );

And update the usage at line 143:

-        <LazyAiChatProvider>{content}</LazyAiChatProvider>
+        <LazyAiChatSection>{content}</LazyAiChatSection>

140-148: Consider adding error handling for lazy-loaded AI components.

If the lazy-loaded AI components fail to load, the Suspense will indefinitely show the fallback with no error indication. Consider wrapping with an ErrorBoundary to provide user feedback and recovery options.

🔎 Example error boundary wrapper
  if (aiLoaded) {
    return (
-      <Suspense fallback={content}>
-        <LazyAiChatProvider>{content}</LazyAiChatProvider>
-      </Suspense>
+      <DashboardErrorBoundary>
+        <Suspense fallback={content}>
+          <LazyAiChatProvider>{content}</LazyAiChatProvider>
+        </Suspense>
+      </DashboardErrorBoundary>
    );
  }

Note: This reuses the existing DashboardErrorBoundary imported at line 12.

src/components/ai/AiChatPopover.tsx (1)

121-128: LGTM! Consider memoizing the local resetChat fallback.

The fallback chain for controlled/context/local state is well-designed and flexible. The inline arrow function for local resetChat (lines 126-127) is recreated on every render, which is a minor performance consideration.

Optional: Memoize local resetChat
  const context = useContext(AiChatPopoverContext);
  const [localOpen, setLocalOpen] = useState(false);
  const [localChat, setLocalChat] = useState(
    () => new Chat<UIMessage>({ id: "ai-popover-local" })
  );
+
+  const localResetChat = useCallback(() => {
+    setLocalChat(new Chat<UIMessage>({ id: `ai-popover-${Date.now()}` }));
+  }, []);

  const open = controlledOpen ?? context?.open ?? localOpen;
  const handleOpenChange = onOpenChange ?? context?.setOpen ?? setLocalOpen;
  const chat = context?.chat ?? localChat;
-  const resetChat =
-    context?.resetChat ??
-    (() =>
-      setLocalChat(new Chat<UIMessage>({ id: `ai-popover-${Date.now()}` })));
+  const resetChat = context?.resetChat ?? localResetChat;
  const quickPrompts = context?.quickPrompts;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 51af003 and 0389523.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (40)
  • .npmrc
  • AGENTS.md
  • app/api/chat/route.ts
  • app/api/chat/tools.ts
  • components.json
  • next-env.d.ts
  • package.json
  • pages/_app.tsx
  • pages/ai.tsx
  • public/mockServiceWorker.js
  • src/components/Configs/ConfigDetailsTabs.tsx
  • src/components/SearchSelect/index.tsx
  • src/components/ai-elements/code-block.tsx
  • src/components/ai-elements/confirmation.tsx
  • src/components/ai-elements/conversation.tsx
  • src/components/ai-elements/loader.tsx
  • src/components/ai-elements/message.tsx
  • src/components/ai-elements/prompt-input.tsx
  • src/components/ai-elements/reasoning.tsx
  • src/components/ai-elements/shimmer.tsx
  • src/components/ai-elements/suggestion.tsx
  • src/components/ai-elements/tool.tsx
  • src/components/ai/AiChat.tsx
  • src/components/ai/AiChatPopover.tsx
  • src/components/ui/alert.tsx
  • src/components/ui/button-group.tsx
  • src/components/ui/button.tsx
  • src/components/ui/command.tsx
  • src/components/ui/dialog.tsx
  • src/components/ui/hover-card.tsx
  • src/components/ui/input-group.tsx
  • src/components/ui/popover.tsx
  • src/components/ui/select.tsx
  • src/components/ui/textarea.tsx
  • src/pages/config/details/ConfigDetailsPage.tsx
  • src/pages/config/details/SendToAiButton.tsx
  • src/ui/Layout/AiFeatureLoader.tsx
  • src/ui/Layout/SearchLayout.tsx
  • src/ui/Layout/SearchLayoutAiChat.tsx
  • tsconfig.json
💤 Files with no reviewable changes (1)
  • src/components/SearchSelect/index.tsx
✅ Files skipped from review due to trivial changes (1)
  • AGENTS.md
🚧 Files skipped from review as they are similar to previous changes (18)
  • src/components/Configs/ConfigDetailsTabs.tsx
  • src/ui/Layout/SearchLayoutAiChat.tsx
  • .npmrc
  • src/components/ui/button.tsx
  • src/components/ai-elements/loader.tsx
  • src/pages/config/details/ConfigDetailsPage.tsx
  • pages/_app.tsx
  • src/components/ai-elements/shimmer.tsx
  • src/components/ui/hover-card.tsx
  • pages/ai.tsx
  • src/components/ui/popover.tsx
  • src/pages/config/details/SendToAiButton.tsx
  • src/components/ai-elements/reasoning.tsx
  • src/components/ai-elements/confirmation.tsx
  • src/components/ai/AiChat.tsx
  • src/ui/Layout/AiFeatureLoader.tsx
  • src/components/ui/dialog.tsx
  • app/api/chat/tools.ts
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-20T08:54:22.331Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ui/progress.tsx:0-0
Timestamp: 2025-12-20T08:54:22.331Z
Learning: Do not modify shadcn/ui components themselves. Keep them in their original form to maintain upstream consistency and simplify upgrades. If you identify improvements, implement them via project-specific wrappers, composition, or separate utility components rather than editing the upstream component files.

Applied to files:

  • src/components/ui/textarea.tsx
  • src/components/ui/select.tsx
  • src/components/ui/command.tsx
  • src/components/ui/button-group.tsx
  • src/components/ui/input-group.tsx
  • src/components/ui/alert.tsx
📚 Learning: 2025-12-20T08:56:21.836Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ai-elements/prompt-input.tsx:1130-1187
Timestamp: 2025-12-20T08:56:21.836Z
Learning: AI SDK components from the registry (ai-elements: https://registry.ai-sdk.dev/) are kept in their original form without modifications, similar to shadcn/ui components. This helps maintain consistency with upstream and simplifies future upgrades.

Applied to files:

  • components.json
  • src/ui/Layout/SearchLayout.tsx
📚 Learning: 2025-12-20T08:56:14.015Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ai-elements/prompt-input.tsx:1130-1187
Timestamp: 2025-12-20T08:56:14.015Z
Learning: Do not modify AI SDK components sourced from the registry (e.g. ai-elements) to keep upstream compatibility and simplify upgrades. Apply this rule across all TSX files under src/components/ai-elements; if you must make an exception, document the reason and prefer non-mutating approaches (wrapping, composition) over direct edits.

Applied to files:

  • src/components/ai-elements/suggestion.tsx
  • src/components/ai-elements/code-block.tsx
  • src/components/ai-elements/tool.tsx
  • src/components/ai-elements/conversation.tsx
  • src/components/ai-elements/message.tsx
  • src/components/ai-elements/prompt-input.tsx
📚 Learning: 2025-12-20T08:54:29.416Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ui/progress.tsx:0-0
Timestamp: 2025-12-20T08:54:29.416Z
Learning: The project uses shadcn/ui components. These components are kept in their original form without modifications, even if potential improvements are identified. This helps maintain consistency with upstream and simplifies future upgrades.

Applied to files:

  • src/components/ai-elements/code-block.tsx
  • src/components/ai-elements/prompt-input.tsx
🧬 Code graph analysis (12)
src/components/ui/textarea.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/select.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai/AiChatPopover.tsx (1)
src/components/ai/AiChat.tsx (1)
  • AIChat (137-455)
src/components/ui/command.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/dialog.tsx (2)
  • Dialog (110-110)
  • DialogContent (115-115)
src/components/ai-elements/suggestion.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/button-group.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/separator.tsx (1)
  • Separator (29-29)
src/components/ai-elements/code-block.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
app/api/chat/route.ts (1)
app/api/chat/tools.ts (1)
  • buildChatTools (92-102)
src/components/ui/input-group.tsx (4)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/input.tsx (1)
  • Input (22-22)
src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
src/components/ai-elements/conversation.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/ui/Layout/SearchLayout.tsx (1)
src/ui/Layout/AiFeatureLoader.tsx (2)
  • useAiFeatureLoader (32-40)
  • AiFeatureLoaderProvider (18-30)
src/components/ui/alert.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
🪛 ast-grep (0.40.3)
src/components/ai-elements/code-block.tsx

[warning] 111-111: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)


[warning] 116-116: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🔇 Additional comments (47)
src/components/ai-elements/suggestion.tsx (4)

1-6: LGTM! Imports and directives are appropriate.

The "use client" directive is correctly placed for client-side React components, and all imports are standard and properly typed.


7-14: LGTM! Clean container component.

The Suggestions component is well-implemented as a simple, flexible wrapper with appropriate flexbox styling for displaying suggestion pills.


16-43: LGTM! Well-implemented suggestion button component.

The type definition properly constrains props by omitting conflicting Button props and adding custom suggestion handling. The component correctly passes the suggestion string to the onClick handler and applies appropriate pill styling.


1-43: Verify this doesn't conflict with upstream registry components.

Based on learnings, components in the ai-elements directory should maintain upstream compatibility. While this is a new file rather than a modification, please confirm that a suggestion component doesn't already exist in the AI SDK registry to avoid future conflicts or upgrade complications.

Based on learnings, components in ai-elements should avoid modifications to maintain upstream compatibility.

src/components/ui/input-group.tsx (5)

9-38: Well-structured InputGroup wrapper with clear variant organization.

The use of CSS :has() selectors for conditional styling based on child elements and data attributes is a clean approach for this composite component. The comments for alignment, focus, and error states improve readability of the complex class composition.


102-119: LGTM!

The approach of omitting Button's size prop and applying custom size variants via inputGroupButtonVariants provides appropriate granular control for input group button sizing.


121-134: LGTM!

Clean implementation for inline text/icon content within the input group.


136-150: LGTM!

The styling resets (border-0, shadow-none, focus-visible:ring-0) properly delegate visual styling to the parent InputGroup wrapper while the data-slot attribute enables the parent's focus state detection.


152-166: LGTM!

Consistent implementation with InputGroupInput, with appropriate textarea-specific styling (resize-none, py-3).

src/components/ui/alert.tsx (4)

1-4: LGTM!

The imports are standard for a shadcn/ui component and correctly include all necessary dependencies.


6-20: LGTM!

The alert variants are well-defined with appropriate styling for default and destructive states, including proper icon positioning.


22-33: LGTM!

The Alert component is correctly implemented with proper ref typing, accessibility attributes, and variant support.


59-59: LGTM!

The exports correctly expose all Alert-related components.

src/components/ui/button-group.tsx (6)

1-6: LGTM!

The imports are well-structured and include all necessary dependencies for the ButtonGroup system: Radix UI for composition, CVA for variants, and internal utilities.


7-22: Well-structured variant system.

The CVA implementation correctly handles orientation variants with appropriate border and radius collapsing. The base className string on line 8 is complex but necessary to handle various composition scenarios (nested groups, select triggers, inputs, focus states).


24-39: LGTM!

The ButtonGroup component correctly implements the variant system with proper type definitions, accessibility attributes (role="group"), and data attributes for styling hooks. The className merging and props forwarding follow React best practices.


41-62: LGTM!

The ButtonGroupText component correctly implements the asChild pattern with proper type definitions and conditional rendering. The styling provides consistent appearance for text containers within button groups, including sensible defaults for SVG sizing.


64-80: LGTM with minor note on margin override.

The ButtonGroupSeparator correctly wraps the Separator component with button-group-specific styling. The default orientation of "vertical" is appropriate for separators in horizontal button groups.

Note: The !m-0 on line 74 uses Tailwind's important flag to override default Separator margins. While this works, it might indicate competing styles between the Separator base component and this wrapper. This is acceptable for composition but worth being aware of.


82-87: LGTM!

The exports are clean and complete, exposing all components and the variants function for external use.

src/components/ai-elements/conversation.tsx (3)

10-20: LGTM! Clean wrapper with sensible defaults.

The Conversation component provides good defaults for the StickToBottom behavior (smooth scrolling on both initial render and resize) and proper accessibility with role="log".


22-34: LGTM! Appropriate layout styling.

The ConversationContent component adds consistent spacing and padding for message content.


36-69: LGTM! Well-designed empty state component.

The component provides sensible defaults while remaining flexible through the children override pattern. Semantic HTML structure is appropriate.

src/components/ui/textarea.tsx (1)

5-22: LGTM! Standard shadcn/ui textarea component.

The Textarea component follows the shadcn/ui pattern correctly with proper ref forwarding, className composition, and standard props spreading. The styling provides good defaults for form inputs.

src/components/ai-elements/prompt-input.tsx (1)

1-1413: Registry component - keeping as-is per project policy.

This file is sourced from the AI SDK registry and follows the same policy as shadcn/ui components: keeping it in its original form without modifications to maintain upstream compatibility and simplify future upgrades.

Based on learnings from previous reviews, no modifications are being suggested.

components.json (1)

21-23: LGTM!

The registry configuration for @ai-elements correctly points to the AI SDK registry, enabling the project to pull AI components using the shadcn CLI. This aligns with the learned project convention of keeping registry components in their original form for easier upgrades.

next-env.d.ts (1)

1-6: LGTM!

The added type reference for Next.js navigation compatibility types is auto-generated by Next.js when using App Router features. This aligns with the new app/api/chat/route.ts introduced in this PR.

tsconfig.json (1)

36-47: LGTM!

The TypeScript configuration updates are appropriate:

  • The Next.js plugin enhances IDE support for App Router features
  • Adding "app" to include is necessary for the new app/api/chat/route.ts
  • Including .next/types/**/*.ts enables proper type generation for Next.js
src/components/ui/select.tsx (1)

1-157: LGTM!

This is a standard shadcn/ui Select component built on Radix UI primitives. The implementation follows the project convention of keeping shadcn components in their original form for upstream compatibility. Based on learnings, no modifications should be made to this component.

package.json (1)

166-167: LGTM on build script memory allocation.

Increasing NODE_OPTIONS=--max-old-space-size=8192 is reasonable given the expanded dependency surface and new AI/UI components. This should prevent OOM issues during builds.

app/api/chat/route.ts (1)

61-94: LGTM on LLM connection fetching.

The fetchLLMConnection function has solid error handling: validates response status, checks content-type before parsing, provides informative error messages with body snippets for debugging, and uses Next.js caching appropriately.

src/components/ai-elements/code-block.tsx (2)

85-97: LGTM on the highlighting effect fix.

The previous highlighting bug has been addressed. The effect now correctly uses a cancelled flag pattern to prevent stale updates after unmount or prop changes, and properly re-highlights when code, language, or showLineNumbers props change.


109-118: Acknowledged XSS consideration for dangerouslySetInnerHTML.

The static analysis warnings about dangerouslySetInnerHTML are noted. However, this is an AI SDK registry component that should not be modified per project conventions. The HTML is generated by Shiki from code strings (not arbitrary user HTML), and the biome-ignore comments acknowledge this necessity. The risk is acceptable given that Shiki's output is deterministic based on the input code.

Based on learnings, AI SDK components from the registry are kept in their original form.

src/components/ai-elements/tool.tsx (3)

1-69: LGTM! Clean component structure and comprehensive state handling.

The imports are well-organized, the Tool wrapper provides consistent styling, and getStatusBadge covers all seven possible tool states with appropriate visual indicators.


102-127: LGTM! Clean content wrappers with appropriate formatting.

ToolContent provides smooth animations, and ToolInput correctly formats tool parameters as JSON for display.


1-172: Verify component origin relative to AI SDK registry.

This is a well-structured, type-safe implementation of Tool UI components. However, based on retrieved learnings, components in ai-elements/ may be sourced from the AI SDK registry to maintain upstream compatibility.

Please confirm whether this is:

  1. A custom component for this project (in which case the minor issues flagged above should be addressed)
  2. A component from the AI SDK registry (in which case modifications should be minimal and documented)

Based on learnings: "Do not modify AI SDK components sourced from the registry (e.g. ai-elements) to keep upstream compatibility and simplify upgrades."

src/components/ui/command.tsx (1)

1-153: LGTM! Well-structured shadcn/ui command component.

This implementation follows shadcn/ui architectural patterns correctly:

  • Proper forwardRef usage and displayName assignments for all components
  • Comprehensive TypeScript typing with ElementRef and ComponentPropsWithoutRef
  • Correct className composition using the cn utility
  • Clean integration with Dialog components from the same UI library

The component uses current library versions (cmdk 1.1.1, @radix-ui/react-dialog 1.1.15, lucide-react 0.525.0) with no known security vulnerabilities. Keep this shadcn/ui component in its original form to maintain upstream consistency and simplify future upgrades. Any customizations should be implemented via project-specific wrappers or composition rather than modifying this file directly.

src/ui/Layout/SearchLayout.tsx (2)

85-109: LGTM! Clean progressive enhancement pattern.

The conditional rendering provides a good user experience:

  • Initial state shows a clickable button to request AI features
  • Loading state shows a disabled button with a pulsing icon
  • Loaded state renders the full AI chat button
  • Proper accessibility attributes throughout

151-157: LGTM! Clean provider wrapper pattern.

The public SearchLayout wrapper correctly provides the AI feature context to descendants while maintaining the existing component API.

src/components/ai-elements/message.tsx (1)

1-448: Note for future maintenance: Keep upstream compatibility.

This file is in the ai-elements directory which contains AI SDK components. Based on project learnings, components in this directory should not be modified to maintain upstream compatibility and simplify upgrades. If future changes are needed, prefer wrapping or composing these components rather than directly editing them.

Based on learnings, ai-elements components should remain unchanged for easier upgrades.

src/components/ai/AiChatPopover.tsx (8)

1-15: LGTM!

The imports are well-organized and include all necessary dependencies for the AI chat popover implementation.


17-35: LGTM!

The type definitions and context setup follow React best practices. The context value shape is comprehensive and well-typed.


48-56: LGTM!

The setOpen callback appropriately clears quick prompts when the popover is closed, ensuring clean state management.


72-83: LGTM!

The useMemo correctly optimizes the context value. The dependency array is complete—setQuickPrompts is a stable setter from useState and doesn't need to be included.


92-102: LGTM!

The custom hook follows React best practices with clear error handling when used outside the provider context.


136-136: Verify necessity of forceMount prop.

The forceMount prop keeps the PopoverContent mounted even when closed, which can impact performance if the AIChat component is heavy. Confirm whether this is required for preserving chat state or can be removed.

Note: The key={chat.id} on AIChat (line 141) already ensures proper remounting when the chat instance changes, so forceMount might not be necessary unless there's a specific reason for keeping the component mounted.


140-147: LGTM!

The AIChat integration is well-structured with proper key usage to force remounts on chat changes, and all necessary props are correctly wired.


43-43: Verify Chat constructor API from @ai-sdk/react version 3.0.3.

The code instantiates Chat<UIMessage> with constructor arguments like { id: string } and { id: string, messages: UIMessage[] } (lines 43, 60, 67). Confirm that these parameter names and structure match the actual @ai-sdk/[email protected] API, particularly whether the messages parameter should be initialMessages or messages.

Also applies to: 58-62, 64-70

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (5)
package.json (3)

34-43: Verify Radix UI version compatibility between v1 and v2 packages.

The codebase mixes Radix UI v1 packages (hover-card, popover, progress, scroll-area, use-controllable-state) with v2 packages (dropdown-menu ^2.1.16, select ^2.2.6). This creates potential runtime issues due to mismatched internal dependencies like @radix-ui/react-focus-scope and @radix-ui/react-dismissable-layer.


79-79: Remove unused effect dependency.

The effect package (^3.19.13) is not imported or used anywhere in the codebase, adding unnecessary bundle size.


64-64: Remove unused Valibot packages from dependencies.

Both @valibot/to-json-schema (^1.5.0) and valibot (^1.2.0) are present but have no actual usage in the codebase. The project uses zod for validation (as seen in app/api/chat/tools.ts), making these packages redundant.

Also applies to: 142-142

public/mockServiceWorker.js (1)

4-11: Service worker version mismatch — regenerate with MSW CLI.

The service worker declares version 1.3.2 (line 5), but package.json specifies MSW ^1.2.3. This auto-generated file should be regenerated to match the installed version:

npx msw init public/ --save

Do not manually edit this file.

app/api/chat/route.ts (1)

150-162: Potential MCP client resource leak if buildChatTools throws.

If buildChatTools (line 162) throws an exception, the execution jumps to the catch block (line 187) without closing the mcpClient created at line 151. The onError/onFinish callbacks only execute during streaming, not during setup.

🔎 Proposed fix
     const modelMessages = await convertToModelMessages(messages);
     const mcpClient = await createMCPClient({
       transport: {
         type: "http",
         url: buildURL(backendUrl, "/mcp").toString(),
         headers: {
           // Use the user's cookie to authenticate for now.
           // We need to add the more fine-grained MCP tokens
           Cookie: cookies
         }
       }
     });
-    const tools = await buildChatTools(mcpClient);
+    let tools;
+    try {
+      tools = await buildChatTools(mcpClient);
+    } catch (error) {
+      await mcpClient.close();
+      throw error;
+    }
🧹 Nitpick comments (8)
src/components/ai-elements/confirmation.tsx (1)

27-36: Remove duplicate type branch.

Lines 27-31 and 32-36 define identical type branches (approved: true with optional reason). This duplication serves no purpose and should be removed.

🔎 Proposed fix
   | {
       id: string;
       approved: true;
       reason?: string;
     }
-  | {
-      id: string;
-      approved: true;
-      reason?: string;
-    }
   | {
       id: string;

Note: Based on learnings, AI SDK registry components under src/components/ai-elements/ should generally not be modified to maintain upstream compatibility. If you choose to keep this file as-is to avoid divergence from the source, document that decision. Otherwise, consider reporting this upstream.

src/ui/Layout/SearchLayout.tsx (1)

50-57: Consider resetting shouldAutoOpen after it's consumed.

The shouldAutoOpen state is set to true but never reset. While this works because aiLoaded gates subsequent clicks, it would be cleaner to reset the flag after the LazyAiChatButton consumes it to prevent stale state if the component tree changes.

🔎 Proposed approach

Pass a callback to LazyAiChatButton to reset the state after it's consumed:

- <LazyAiChatButton shouldAutoOpen={shouldAutoOpen} />
+ <LazyAiChatButton 
+   shouldAutoOpen={shouldAutoOpen} 
+   onAutoOpenConsumed={() => setShouldAutoOpen(false)} 
+ />

This would require updating the AiChatButton component to call the callback after opening.

src/ui/Layout/AiFeatureLoader.tsx (1)

42-50: Consider wrapping children in a fragment for type safety.

Returning children directly can cause issues when multiple children are passed (as it would return an array, which isn't a valid JSX element in some scenarios). Wrapping in a fragment ensures consistent return type.

🔎 Proposed fix
 export function AiFeatureRequest({ children }: { children: ReactNode }) {
   const { requestAiFeatures } = useAiFeatureLoader();
 
   useEffect(() => {
     requestAiFeatures();
   }, [requestAiFeatures]);
 
-  return children;
+  return <>{children}</>;
 }
src/components/ui/button-group.tsx (1)

41-62: Consider improving type safety for the asChild pattern.

The current union type approach allows any combination of div and Slot props regardless of the asChild value. A discriminated union would provide better type safety and developer experience.

🔎 Suggested refactor using function overloads
+function ButtonGroupText(
+  props: React.ComponentPropsWithoutRef<"div"> & { asChild?: false }
+): JSX.Element;
+function ButtonGroupText(
+  props: React.ComponentPropsWithoutRef<typeof Slot> & { asChild: true }
+): JSX.Element;
 function ButtonGroupText({
   className,
   asChild = false,
   ...props
-}: (
-  | React.ComponentPropsWithoutRef<"div">
-  | React.ComponentPropsWithoutRef<typeof Slot>
-) & {
-  asChild?: boolean;
-}) {
+}: any) {
   const Comp = asChild ? Slot : "div";

   return (
     <Comp
       className={cn(
         "shadow-xs flex items-center gap-2 rounded-md border bg-muted px-4 text-sm font-medium [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
         className
       )}
       {...props}
     />
   );
 }
src/pages/config/details/SendToAiButton.tsx (4)

47-64: Consider defining types for the API response.

Using (json as any)?.configs?.[0]?.type on line 50 bypasses TypeScript's type safety. While the code handles undefined gracefully with optional chaining, defining proper types for the expected API response structure would catch API contract changes at compile time and improve maintainability.

🔎 Proposed refactor

Define a type for the expected response structure:

type AiContextResponse = {
  configs?: Array<{
    type?: string;
    // other fields...
  }>;
  // other fields...
};

Then update the parsing:

-     const json = (await response.json()) as unknown;
+     const json = (await response.json()) as AiContextResponse;

      // Safely extract the config type with null/undefined checks
-     const configType = (json as any)?.configs?.[0]?.type;
+     const configType = json.configs?.[0]?.type;

66-75: Consider removing the unnecessary filter.

The .filter(Boolean) on line 74 is unnecessary since all array elements are string literals that are always truthy. The filter adds no value and can be safely removed for cleaner code.

🔎 Proposed change
      const text = [
        "Config details and its related configs:",
        "```json",
        jsonText,
        "```"
      ]
-       .filter(Boolean)
        .join("\n\n");

86-90: Improve error handling type safety.

The error as any assertion on line 87 defeats TypeScript's purpose. Consider properly typing the error or using a type guard to extract meaningful error information.

🔎 Proposed refactor
    } catch (error) {
-     toastError(error as any);
+     const errorMessage = error instanceof Error ? error.message : 'An unexpected error occurred';
+     toastError(errorMessage);
    } finally {

29-34: Consider adding a timeout to the fetch request.

The API call lacks a timeout or abort signal, which could result in the button remaining in a "Sending..." state indefinitely if the request hangs. This degrades the user experience when network issues occur.

🔎 Proposed enhancement
    try {
      const url = new URL(
        `/api/llm/context/config/${encodeURIComponent(configId)}`,
        window.location.origin
      );
-     const response = await fetch(url.toString(), { method: "GET" });
+     const controller = new AbortController();
+     const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
+     
+     try {
+       const response = await fetch(url.toString(), { 
+         method: "GET",
+         signal: controller.signal 
+       });
+       clearTimeout(timeoutId);
+       
+       // ... rest of the code
+     } catch (fetchError) {
+       clearTimeout(timeoutId);
+       if (fetchError instanceof Error && fetchError.name === 'AbortError') {
+         toastError('Request timed out. Please try again.');
+         return;
+       }
+       throw fetchError;
+     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 51af003 and 0389523.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (40)
  • .npmrc
  • AGENTS.md
  • app/api/chat/route.ts
  • app/api/chat/tools.ts
  • components.json
  • next-env.d.ts
  • package.json
  • pages/_app.tsx
  • pages/ai.tsx
  • public/mockServiceWorker.js
  • src/components/Configs/ConfigDetailsTabs.tsx
  • src/components/SearchSelect/index.tsx
  • src/components/ai-elements/code-block.tsx
  • src/components/ai-elements/confirmation.tsx
  • src/components/ai-elements/conversation.tsx
  • src/components/ai-elements/loader.tsx
  • src/components/ai-elements/message.tsx
  • src/components/ai-elements/prompt-input.tsx
  • src/components/ai-elements/reasoning.tsx
  • src/components/ai-elements/shimmer.tsx
  • src/components/ai-elements/suggestion.tsx
  • src/components/ai-elements/tool.tsx
  • src/components/ai/AiChat.tsx
  • src/components/ai/AiChatPopover.tsx
  • src/components/ui/alert.tsx
  • src/components/ui/button-group.tsx
  • src/components/ui/button.tsx
  • src/components/ui/command.tsx
  • src/components/ui/dialog.tsx
  • src/components/ui/hover-card.tsx
  • src/components/ui/input-group.tsx
  • src/components/ui/popover.tsx
  • src/components/ui/select.tsx
  • src/components/ui/textarea.tsx
  • src/pages/config/details/ConfigDetailsPage.tsx
  • src/pages/config/details/SendToAiButton.tsx
  • src/ui/Layout/AiFeatureLoader.tsx
  • src/ui/Layout/SearchLayout.tsx
  • src/ui/Layout/SearchLayoutAiChat.tsx
  • tsconfig.json
💤 Files with no reviewable changes (1)
  • src/components/SearchSelect/index.tsx
✅ Files skipped from review due to trivial changes (1)
  • AGENTS.md
🚧 Files skipped from review as they are similar to previous changes (22)
  • src/components/ui/hover-card.tsx
  • tsconfig.json
  • src/components/ai-elements/suggestion.tsx
  • pages/_app.tsx
  • src/components/ai-elements/loader.tsx
  • src/components/Configs/ConfigDetailsTabs.tsx
  • pages/ai.tsx
  • src/components/ai-elements/shimmer.tsx
  • src/components/ui/button.tsx
  • src/pages/config/details/ConfigDetailsPage.tsx
  • components.json
  • next-env.d.ts
  • src/components/ui/popover.tsx
  • src/ui/Layout/SearchLayoutAiChat.tsx
  • src/components/ai/AiChat.tsx
  • src/components/ui/dialog.tsx
  • .npmrc
  • src/components/ui/alert.tsx
  • src/components/ai-elements/conversation.tsx
  • src/components/ai-elements/tool.tsx
  • src/components/ai/AiChatPopover.tsx
  • src/components/ai-elements/prompt-input.tsx
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-20T08:56:21.836Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ai-elements/prompt-input.tsx:1130-1187
Timestamp: 2025-12-20T08:56:21.836Z
Learning: AI SDK components from the registry (ai-elements: https://registry.ai-sdk.dev/) are kept in their original form without modifications, similar to shadcn/ui components. This helps maintain consistency with upstream and simplifies future upgrades.

Applied to files:

  • package.json
  • src/ui/Layout/SearchLayout.tsx
📚 Learning: 2025-12-20T08:54:22.331Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ui/progress.tsx:0-0
Timestamp: 2025-12-20T08:54:22.331Z
Learning: Do not modify shadcn/ui components themselves. Keep them in their original form to maintain upstream consistency and simplify upgrades. If you identify improvements, implement them via project-specific wrappers, composition, or separate utility components rather than editing the upstream component files.

Applied to files:

  • src/components/ui/textarea.tsx
  • src/components/ui/button-group.tsx
  • src/components/ui/input-group.tsx
  • src/components/ui/select.tsx
  • src/components/ui/command.tsx
📚 Learning: 2025-12-20T08:56:14.015Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ai-elements/prompt-input.tsx:1130-1187
Timestamp: 2025-12-20T08:56:14.015Z
Learning: Do not modify AI SDK components sourced from the registry (e.g. ai-elements) to keep upstream compatibility and simplify upgrades. Apply this rule across all TSX files under src/components/ai-elements; if you must make an exception, document the reason and prefer non-mutating approaches (wrapping, composition) over direct edits.

Applied to files:

  • src/components/ai-elements/confirmation.tsx
  • src/components/ai-elements/code-block.tsx
  • src/components/ai-elements/reasoning.tsx
  • src/components/ai-elements/message.tsx
📚 Learning: 2025-12-20T08:54:29.416Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ui/progress.tsx:0-0
Timestamp: 2025-12-20T08:54:29.416Z
Learning: The project uses shadcn/ui components. These components are kept in their original form without modifications, even if potential improvements are identified. This helps maintain consistency with upstream and simplifies future upgrades.

Applied to files:

  • src/components/ai-elements/code-block.tsx
🧬 Code graph analysis (10)
src/components/ui/textarea.tsx (2)
src/hooks/usePrevious.js (1)
  • ref (4-4)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/confirmation.tsx (3)
src/components/ui/alert.tsx (2)
  • Alert (59-59)
  • AlertDescription (59-59)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
app/api/chat/route.ts (1)
app/api/chat/tools.ts (1)
  • buildChatTools (92-102)
src/components/ui/button-group.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/separator.tsx (1)
  • Separator (29-29)
src/components/ui/input-group.tsx (4)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/input.tsx (1)
  • Input (22-22)
src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
src/components/ai-elements/reasoning.tsx (3)
src/components/ui/collapsible.tsx (3)
  • Collapsible (9-9)
  • CollapsibleTrigger (9-9)
  • CollapsibleContent (9-9)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/shimmer.tsx (1)
  • Shimmer (64-64)
src/pages/config/details/SendToAiButton.tsx (1)
src/components/ai/AiChatPopover.tsx (1)
  • useAiChatPopover (92-102)
src/components/ui/select.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/command.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/dialog.tsx (2)
  • Dialog (110-110)
  • DialogContent (115-115)
src/components/ai-elements/message.tsx (4)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/tooltip.tsx (4)
  • TooltipProvider (30-30)
  • Tooltip (30-30)
  • TooltipTrigger (30-30)
  • TooltipContent (30-30)
src/components/ui/button-group.tsx (2)
  • ButtonGroup (83-83)
  • ButtonGroupText (85-85)
🪛 ast-grep (0.40.3)
src/components/ai-elements/code-block.tsx

[warning] 111-111: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)


[warning] 116-116: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🔇 Additional comments (46)
src/components/ui/command.tsx (6)

1-10: Imports and directive look correct.

The "use client" directive is appropriate for this component since it uses React refs and client-side interactivity. Imports are well-organized with external dependencies first, followed by internal utilities.


11-24: Command wrapper implementation is correct.

The component properly forwards the ref, merges className with the cn utility, and spreads remaining props. The displayName is correctly set from the underlying primitive.


26-36: CommandDialog integrates Dialog and Command correctly.

The component composes the Dialog and Command primitives appropriately. The extensive class targeting via [&_[cmdk-*]] selectors on line 30 ensures consistent styling for cmdk internal elements when rendered inside a dialog context.


38-55: CommandInput implementation is correct.

The cmdk-input-wrapper="" attribute on line 42 enables the CSS targeting defined in CommandDialog. The Search icon and input styling follow typical shadcn/ui patterns.


57-125: CommandList, CommandEmpty, CommandGroup, CommandSeparator, and CommandItem are well-implemented.

All components:

  • Properly forward refs to underlying cmdk primitives
  • Use cn utility for className merging
  • Set displayName from the underlying primitive
  • Apply appropriate styling with data-attribute selectors for state (disabled, selected)

127-153: CommandShortcut and exports are correct.

CommandShortcut is a simple styled span without ref forwarding (appropriate for its use case). All components are properly exported as named exports for consumption by other modules like PromptInputCommand*.

Based on learnings, this is a standard shadcn/ui component and should be kept in its original form to maintain upstream consistency and simplify upgrades.

src/components/ai-elements/confirmation.tsx (1)

1-26: Well-structured confirmation UI implementation.

The component architecture is sound:

  • Context and hook properly enforce provider usage with clear error messages
  • State-based conditional rendering is consistent across all sub-components
  • Component composition allows flexible confirmation flows
  • Type safety is strong with discriminated unions and exported prop types

Note: Based on learnings, this file is sourced from the AI SDK registry and should remain unmodified to simplify upgrades and maintain upstream compatibility.

Also applies to: 37-178

src/components/ai-elements/code-block.tsx (2)

85-97: Previous issue addressed correctly.

The cancelled flag pattern now ensures the component properly re-highlights when code, language, or showLineNumbers props change, while preventing state updates after unmount. This correctly addresses the bug from the previous review.


111-117: Static analysis warnings are false positives.

The dangerouslySetInnerHTML usage is safe here because the HTML is generated by Shiki's codeToHtml function, a trusted syntax highlighting library. The content is not user-provided input, so there's no XSS risk. The biome-ignore comments correctly acknowledge this intentional usage.

Based on learnings, this is an AI SDK component from the registry that should remain unmodified for upstream compatibility.

src/ui/Layout/SearchLayout.tsx (4)

16-27: LGTM! Lazy loading pattern is well implemented.

The dynamic imports with module transformation correctly enable code splitting for the AI SDK components, keeping the main bundle lean until AI features are requested.


85-109: LGTM! Good UX for the loading transition.

The conditional rendering provides a smooth transition from the trigger button to the fully loaded AI chat button, with an appropriate loading indicator during the Suspense boundary resolution. Accessibility is well-handled with aria-hidden and sr-only text.


139-149: LGTM! Smart fallback pattern.

Using content as the Suspense fallback ensures seamless UX during the lazy load—users see the same content while the AI provider loads in the background.


151-157: LGTM!

Clean wrapper pattern that ensures the AI feature loading context is available throughout the layout.

src/ui/Layout/AiFeatureLoader.tsx (3)

1-16: LGTM!

Clean type definition and context setup with proper null initialization for provider detection.


18-30: LGTM! Past review feedback addressed.

The requestAiFeatures function is now properly memoized with useCallback.

Minor nitpick: setAiLoaded is guaranteed stable by React, so the dependency array could be empty ([]), but including it is harmless.


32-40: LGTM!

Standard pattern for context hooks with a clear error message when used outside the provider.

src/components/ui/button-group.tsx (4)

1-5: LGTM! Clean imports.

All imports are appropriate and follow the project's UI component conventions.


7-22: LGTM! Comprehensive variant implementation.

The buttonGroupVariants effectively handles horizontal and vertical orientations with appropriate border and rounding adjustments for grouped appearance. The complex selectors for focus management, nested groups, and select triggers are well-considered.


24-39: LGTM! Solid ButtonGroup implementation.

The component properly combines variant-based styling with accessibility attributes (role="group") and data attributes for CSS targeting. Type definitions and prop forwarding are well-structured.


64-80: LGTM! Separator implementation with correct defaults.

The default orientation="vertical" is appropriate here—horizontal button groups typically use vertical separators, making this a sensible default that works well with ButtonGroup's horizontal default.

src/components/ai-elements/reasoning.tsx (3)

65-91: Verify auto-close behavior across multiple streaming sessions.

The hasAutoClosed state (line 65) is set to true on line 86 but never reset to false. If this component handles multiple streaming sessions (e.g., the user asks multiple AI questions in sequence), the auto-close behavior will only work for the first session. After the first auto-close, hasAutoClosed remains true permanently, preventing future auto-closes.

Consider whether hasAutoClosed should be reset to false when streaming starts again (in the effect on lines 69-78, when isStreaming becomes true).

Based on learnings, this is a registry component. If this behavior is intentional from upstream, document the rationale; otherwise, verify if auto-close should work for all streaming sessions.

Potential fix if auto-close should work for multiple sessions
 useEffect(() => {
   if (isStreaming) {
     if (startTime === null) {
       setStartTime(Date.now());
+      setHasAutoClosed(false);
     }
   } else if (startTime !== null) {
     setDuration(Math.ceil((Date.now() - startTime) / MS_IN_S));
     setStartTime(null);
   }
 }, [isStreaming, startTime, setDuration]);

130-162: LGTM - ReasoningTrigger implementation.

The trigger component correctly consumes context via useReasoning() and provides sensible defaults with Brain/Chevron icons and thinking status messages. The controlled/uncontrolled pattern with getThinkingMessage customization is well-designed.


170-183: LGTM - ReasoningContent implementation.

The component correctly wraps CollapsibleContent with Streamdown for markdown rendering. The animation classes and prop spreading are appropriate.

src/components/ui/textarea.tsx (1)

1-22: LGTM! Standard shadcn/ui component implementation.

The Textarea component follows the established shadcn/ui pattern with proper ref forwarding, className composition, and comprehensive styling for focus, disabled, and placeholder states.

Based on learnings, this shadcn/ui component is kept in its original form to maintain upstream consistency.

src/components/ui/input-group.tsx (4)

9-38: LGTM! Well-structured InputGroup wrapper.

The component properly handles multiple variants (alignment, focus, error states) using modern CSS has-[] selectors and data-slot attributes for composed behavior.


84-119: LGTM! Well-designed button wrapper with sensible defaults.

The component properly wraps the Button primitive with InputGroup-specific size variants and defaults to type="button" to prevent unintended form submissions.


121-134: LGTM! Clean text container implementation.

Simple and effective wrapper for displaying text and icons within the input group.


136-166: LGTM! Properly coordinated input and textarea wrappers.

Both components correctly use data-slot="input-group-control" to coordinate with the parent InputGroup for focus ring and styling, while removing their own borders and shadows.

src/components/ui/select.tsx (1)

1-157: LGTM! Complete and well-structured Radix UI Select wrapper.

The implementation properly wraps all Radix UI Select primitives with consistent styling, animations, and proper ref forwarding. The SelectContent includes scroll buttons and portal rendering, while SelectItem features a check indicator for selected states.

Based on learnings, this shadcn/ui component is kept in its original form to maintain upstream consistency.

src/pages/config/details/SendToAiButton.tsx (3)

1-10: LGTM!

The imports and type definition are clean and follow best practices.


12-15: LGTM!

The hook usage and state management are appropriate. The disabled flag correctly handles all edge cases.


93-104: LGTM!

The button render logic is clean and provides appropriate visual feedback to users. The conditional text and disabled state handle the loading experience well.

package.json (2)

11-14: AI SDK packages properly updated to stable releases.

The @AI-SDK packages and the ai package are now on stable releases with appropriate semantic versioning ranges. This addresses previous concerns about using beta versions in production.

Also applies to: 66-66


166-167: Build memory allocation increased for AI features.

The 8GB memory limit (--max-old-space-size=8192) is a reasonable adjustment for builds incorporating AI SDK components, complex UI libraries, and larger bundle sizes. Monitor actual memory usage during CI/CD to ensure this allocation is sufficient but not excessive.

app/api/chat/tools.ts (5)

1-2: Imports are correct for AI SDK and Zod v4.

The imports from ai and zod are appropriate for the tool definitions that follow.


4-25: Approval logic correctly implements read-only tool exemptions.

The combination of the explicit allowlist and the startsWith("view_") check (line 35) properly exempts read-only operations from approval requirements, as requested in previous feedback.

Also applies to: 33-35


50-90: Zod v4 schemas correctly defined for plot_timeseries tool.

The schema definitions use Zod v4 API correctly:

  • .describe() methods on primitives (lines 60-63, 67)
  • .min(1) constraint on array (line 66)
  • .optional() modifier (line 79)
  • z.literal() for exact value matching (line 72)

The zodSchema() wrapper from the AI SDK properly integrates these schemas into the tool definition.


82-88: Execute function correctly implements tool contract.

The function properly transforms input to the expected output schema format with a sensible default title.


92-102: Tool building logic is correct and well-structured.

The function properly:

  1. Fetches MCP tools from the client
  2. Applies approval requirements via wrapping
  3. Augments with the custom plot_timeseries tool

The type signature { tools: () => Promise<McpTools> } provides sufficient structural typing for the MCP client dependency.

app/api/chat/route.ts (7)

1-20: Imports and type definitions are well-organized.

The imports cover all necessary AI SDK functionality, authentication, and internal dependencies. The LLMConnection type properly models the expected backend configuration structure.


22-50: Backend URL resolution correctly handles multiple auth strategies.

The function properly supports both Clerk-based organization metadata and environment variable fallback, with clear error messages for misconfiguration.


52-59: URL construction helper is correct.

The normalization logic prevents common URL path issues (double slashes, missing slashes) and leverages the standard URL API.


61-94: Excellent error handling for LLM configuration loading.

The function demonstrates robust error handling:

  • Content-type validation before parsing
  • Detailed error messages with response snippets
  • Reasonable caching strategy (5 minutes)
  • Cookie-based authentication passthrough

This defensive approach will significantly ease debugging of configuration issues.


96-120: Model resolution and instantiation logic is clean and correct.

The helper functions properly:

  • Extract optional model IDs from connection config
  • Create provider instances with appropriate defaults
  • Handle unsupported LLM types with clear errors

Model identifiers claude-haiku-4-5 and gpt-4.1 are valid per previous verification.


164-186: Streaming configuration is well-structured with proper lifecycle management.

The streaming setup includes:

  • Reasonable system prompt and step limit
  • Proper MCP client cleanup in both onError and onFinish callbacks
  • Comprehensive event logging for observability
  • sendReasoning: true for transparent AI decision visibility

187-197: Error handling and observability are well-implemented.

The error handling:

  • Returns generic 500 response without leaking internal details
  • Comprehensive structured logging via wideEvent
  • Proper log level distinction (error vs info)
  • Guaranteed logging via finally block

This provides excellent operational visibility for debugging and monitoring.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
app/api/chat/route.ts (1)

172-183: MCP client leak if buildChatTools throws.

If buildChatTools throws an exception, the mcpClient created at line 172 will not be closed because the onError/onFinish callbacks are only invoked during streaming, not during setup. This was flagged in a previous review and remains unaddressed.

🔎 Proposed fix
     const modelMessages = await convertToModelMessages(messages);
     const mcpClient = await createMCPClient({
       transport: {
         type: "http",
         url: buildURL(backendUrl, "/mcp").toString(),
         headers: {
           // Use the user's cookie to authenticate for now.
           // We need to add the more fine-grained MCP tokens
           Cookie: cookies
         }
       }
     });
-    const tools = await buildChatTools(mcpClient);
+    
+    let tools;
+    try {
+      tools = await buildChatTools(mcpClient);
+    } catch (error) {
+      await mcpClient?.close();
+      throw error;
+    }
 
     const result = streamText({

Based on learnings, this issue was previously flagged but not yet resolved.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0389523 and 2c118e2.

📒 Files selected for processing (3)
  • app/api/chat/route.ts
  • src/components/ErrorViewer.tsx
  • src/components/ai/AiChat.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/ai/AiChat.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
app/api/chat/route.ts (1)
app/api/chat/tools.ts (1)
  • buildChatTools (92-102)
🔇 Additional comments (9)
src/components/ErrorViewer.tsx (1)

114-128: LGTM! Recursive JSON parsing handles nested error structures.

The implementation correctly attempts to parse JSON-formatted error messages and recursively processes them. The try-catch ensures graceful fallback to the original error message and stack trace.

app/api/chat/route.ts (8)

1-32: LGTM! Clean imports and type definitions.

The imports are well-organized, the LLMConnection type clearly defines the expected structure, and the custom HttpError class follows standard error handling patterns.


34-62: LGTM! Backend URL resolution handles multiple auth strategies.

The function correctly handles both Clerk-based authentication (extracting from org metadata) and environment variable fallback. Error messages clearly indicate missing configuration. The Clerk API calls are appropriately protected by the outer try-catch in the POST handler.


64-71: LGTM! Clean URL construction utility.

The function properly normalizes both base and endpoint paths before constructing the URL, preventing common issues with double or missing slashes.


73-115: LGTM! Comprehensive response validation and error handling.

The function includes robust checks for HTTP status, content-type, and JSON parsing. Error messages include body snippets for debugging, and the 5-minute cache strikes a good balance between performance and freshness. The use of HttpError ensures proper HTTP status propagation.


117-141: LGTM! Clear model construction logic.

The functions correctly extract model identifiers and build provider-specific model instances. The default models are valid (as confirmed in previous reviews), and the error handling for unsupported types is appropriate.


143-170: LGTM! Clean request validation and telemetry setup.

The handler properly validates the request body, tracks comprehensive metrics in wideEvent, and establishes the necessary backend connections. The telemetry structure will be valuable for debugging and monitoring.


185-207: LGTM! Proper streaming configuration with cleanup.

The streaming setup correctly configures the model, tools, and system prompt. The stopWhen condition prevents runaway iterations, and both onError and onFinish callbacks properly close the MCP client. The sendReasoning: true flag enables transparency in the UI.


208-222: LGTM! Comprehensive error handling and telemetry.

The error handling properly distinguishes between HttpError (with custom status codes) and generic errors. The finally block ensures that telemetry is always logged, with appropriate log levels (error vs. info) based on status. The structured JSON logging will be valuable for monitoring and debugging.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
src/ui/Layout/SearchLayout.tsx (1)

50-57: Reset shouldAutoOpen after auto-open occurs.

The shouldAutoOpen state is set to true in handleAiButtonClick but never reset. While the component may internally guard against re-opening, explicitly resetting this state improves clarity and prevents potential issues if the guard is removed.

🔎 Proposed fix using a callback

Add a callback prop to LazyAiChatButton that resets the state after auto-open:

-<LazyAiChatButton shouldAutoOpen={shouldAutoOpen} />
+<LazyAiChatButton 
+  shouldAutoOpen={shouldAutoOpen} 
+  onAutoOpen={() => setShouldAutoOpen(false)} 
+/>
public/mockServiceWorker.js (1)

4-11: Verify MSW version alignment with package.json.

This auto-generated file declares version 1.3.2. Ensure package.json specifies a compatible MSW version and that this file was regenerated using npx msw init public/ --save rather than manually edited.

#!/bin/bash
# Verify MSW version in package.json matches the service worker
echo "Package.json MSW version:"
grep -E '"msw"' package.json || echo "MSW not found in dependencies"
app/api/chat/route.ts (1)

172-183: MCP client leak if buildChatTools throws.

If createMCPClient succeeds but buildChatTools throws, the mcpClient won't be closed. The onError/onFinish callbacks are only invoked during streaming, not for errors before streamText is called.

🔎 Proposed fix
     const mcpClient = await createMCPClient({
       transport: {
         type: "http",
         url: buildURL(backendUrl, "/mcp").toString(),
         headers: {
           // Use the user's cookie to authenticate for now.
           // We need to add the more fine-grained MCP tokens
           Cookie: cookies
         }
       }
     });
-    const tools = await buildChatTools(mcpClient);
+    let tools;
+    try {
+      tools = await buildChatTools(mcpClient);
+    } catch (error) {
+      await mcpClient.close();
+      throw error;
+    }
🧹 Nitpick comments (3)
src/components/ui/button-group.tsx (2)

7-22: Consider splitting the base className for readability.

The className string on line 8 is quite long and contains multiple complex Tailwind selectors. While functionally correct, splitting it into multiple lines would improve maintainability.

🔎 Proposed refactor for readability
 const buttonGroupVariants = cva(
-  "flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
+  [
+    "flex w-fit items-stretch",
+    "has-[>[data-slot=button-group]]:gap-2",
+    "[&>*]:focus-visible:relative [&>*]:focus-visible:z-10",
+    "has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md",
+    "[&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit",
+    "[&>input]:flex-1"
+  ].join(" "),
   {
     variants: {

41-62: Consider improving type safety with conditional types.

The current union type for props works but could be refined for better type safety. When asChild is true, TypeScript should enforce Slot props; when false, div props.

🔎 Proposed type refinement
+type ButtonGroupTextProps =
+  | ({ asChild?: false } & React.ComponentPropsWithoutRef<"div">)
+  | ({ asChild: true } & React.ComponentPropsWithoutRef<typeof Slot>);
+
 function ButtonGroupText({
   className,
   asChild = false,
   ...props
-}: (
-  | React.ComponentPropsWithoutRef<"div">
-  | React.ComponentPropsWithoutRef<typeof Slot>
-) & {
-  asChild?: boolean;
-}) {
+}: ButtonGroupTextProps) {
app/api/chat/route.ts (1)

144-148: Consider emitting the wideEvent to a structured logging/observability system.

The wideEvent object collects useful telemetry (LLM provider, token usage, finish reason) but is only logged to console. For production observability, consider emitting to a structured logging service or metrics system.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c118e2 and 0a8cb85.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (40)
  • .npmrc
  • AGENTS.md
  • app/api/chat/route.ts
  • app/api/chat/tools.ts
  • components.json
  • next-env.d.ts
  • package.json
  • pages/_app.tsx
  • pages/ai.tsx
  • public/mockServiceWorker.js
  • src/components/Configs/ConfigDetailsTabs.tsx
  • src/components/ErrorViewer.tsx
  • src/components/ai-elements/code-block.tsx
  • src/components/ai-elements/confirmation.tsx
  • src/components/ai-elements/conversation.tsx
  • src/components/ai-elements/loader.tsx
  • src/components/ai-elements/message.tsx
  • src/components/ai-elements/prompt-input.tsx
  • src/components/ai-elements/reasoning.tsx
  • src/components/ai-elements/shimmer.tsx
  • src/components/ai-elements/suggestion.tsx
  • src/components/ai-elements/tool.tsx
  • src/components/ai/AiChat.tsx
  • src/components/ai/AiChatPopover.tsx
  • src/components/ui/alert.tsx
  • src/components/ui/button-group.tsx
  • src/components/ui/button.tsx
  • src/components/ui/command.tsx
  • src/components/ui/dialog.tsx
  • src/components/ui/hover-card.tsx
  • src/components/ui/input-group.tsx
  • src/components/ui/popover.tsx
  • src/components/ui/select.tsx
  • src/components/ui/textarea.tsx
  • src/pages/config/details/ConfigDetailsPage.tsx
  • src/pages/config/details/SendToAiButton.tsx
  • src/ui/Layout/AiFeatureLoader.tsx
  • src/ui/Layout/SearchLayout.tsx
  • src/ui/Layout/SearchLayoutAiChat.tsx
  • tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (24)
  • components.json
  • src/components/ai-elements/shimmer.tsx
  • src/components/ErrorViewer.tsx
  • src/components/ui/textarea.tsx
  • package.json
  • src/ui/Layout/SearchLayoutAiChat.tsx
  • pages/ai.tsx
  • src/components/Configs/ConfigDetailsTabs.tsx
  • src/components/ui/alert.tsx
  • src/components/ai-elements/suggestion.tsx
  • next-env.d.ts
  • AGENTS.md
  • src/components/ai-elements/loader.tsx
  • src/components/ui/button.tsx
  • app/api/chat/tools.ts
  • src/pages/config/details/SendToAiButton.tsx
  • src/components/ai-elements/confirmation.tsx
  • src/components/ui/input-group.tsx
  • src/ui/Layout/AiFeatureLoader.tsx
  • tsconfig.json
  • src/components/ui/select.tsx
  • src/components/ai/AiChatPopover.tsx
  • .npmrc
  • src/components/ai-elements/prompt-input.tsx
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-20T08:54:22.331Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ui/progress.tsx:0-0
Timestamp: 2025-12-20T08:54:22.331Z
Learning: Do not modify shadcn/ui components themselves. Keep them in their original form to maintain upstream consistency and simplify upgrades. If you identify improvements, implement them via project-specific wrappers, composition, or separate utility components rather than editing the upstream component files.

Applied to files:

  • src/components/ui/popover.tsx
  • src/components/ui/dialog.tsx
  • src/components/ui/button-group.tsx
  • src/components/ui/hover-card.tsx
  • src/components/ui/command.tsx
📚 Learning: 2025-12-20T08:56:14.015Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ai-elements/prompt-input.tsx:1130-1187
Timestamp: 2025-12-20T08:56:14.015Z
Learning: Do not modify AI SDK components sourced from the registry (e.g. ai-elements) to keep upstream compatibility and simplify upgrades. Apply this rule across all TSX files under src/components/ai-elements; if you must make an exception, document the reason and prefer non-mutating approaches (wrapping, composition) over direct edits.

Applied to files:

  • src/components/ai-elements/conversation.tsx
  • src/components/ai-elements/reasoning.tsx
  • src/components/ai-elements/tool.tsx
  • src/components/ai-elements/message.tsx
  • src/components/ai-elements/code-block.tsx
📚 Learning: 2025-12-20T08:54:29.416Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ui/progress.tsx:0-0
Timestamp: 2025-12-20T08:54:29.416Z
Learning: The project uses shadcn/ui components. These components are kept in their original form without modifications, even if potential improvements are identified. This helps maintain consistency with upstream and simplifies future upgrades.

Applied to files:

  • src/components/ai/AiChat.tsx
  • src/components/ai-elements/code-block.tsx
📚 Learning: 2025-12-20T08:56:21.836Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ai-elements/prompt-input.tsx:1130-1187
Timestamp: 2025-12-20T08:56:21.836Z
Learning: AI SDK components from the registry (ai-elements: https://registry.ai-sdk.dev/) are kept in their original form without modifications, similar to shadcn/ui components. This helps maintain consistency with upstream and simplifies future upgrades.

Applied to files:

  • src/components/ai/AiChat.tsx
  • src/ui/Layout/SearchLayout.tsx
🧬 Code graph analysis (12)
pages/_app.tsx (1)
src/components/ai/AiChatPopover.tsx (1)
  • AiChatPopoverProvider (37-90)
src/pages/config/details/ConfigDetailsPage.tsx (2)
src/ui/Layout/AiFeatureLoader.tsx (1)
  • AiFeatureRequest (42-50)
src/ui/Buttons/Button.tsx (1)
  • Button (26-101)
app/api/chat/route.ts (1)
app/api/chat/tools.ts (1)
  • buildChatTools (92-102)
src/components/ui/popover.tsx (2)
src/hooks/usePrevious.js (1)
  • ref (4-4)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/conversation.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/dialog.tsx (2)
src/hooks/usePrevious.js (1)
  • ref (4-4)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/reasoning.tsx (3)
src/components/ui/collapsible.tsx (3)
  • Collapsible (9-9)
  • CollapsibleTrigger (9-9)
  • CollapsibleContent (9-9)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/shimmer.tsx (1)
  • Shimmer (64-64)
src/components/ui/button-group.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/separator.tsx (1)
  • Separator (29-29)
src/components/ai-elements/tool.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/code-block.tsx (1)
  • CodeBlock (74-128)
src/components/ai-elements/message.tsx (4)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/components/ui/tooltip.tsx (4)
  • TooltipProvider (30-30)
  • Tooltip (30-30)
  • TooltipTrigger (30-30)
  • TooltipContent (30-30)
src/components/ui/button-group.tsx (2)
  • ButtonGroup (83-83)
  • ButtonGroupText (85-85)
src/components/ui/hover-card.tsx (2)
src/hooks/usePrevious.js (1)
  • ref (4-4)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ai-elements/code-block.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
🪛 ast-grep (0.40.3)
src/components/ai-elements/code-block.tsx

[warning] 111-111: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)


[warning] 116-116: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🔇 Additional comments (19)
src/pages/config/details/ConfigDetailsPage.tsx (3)

6-17: LGTM!

The imports are well-organized and appropriate for the lazy-loaded AI feature integration. Using lazy and Suspense for code splitting the AI SDK is a good practice to avoid bloating the main bundle.


19-24: LGTM!

The lazy loading pattern correctly handles the named export conversion, and the explanatory comment clarifies the intent for code splitting.


98-113: Well-structured lazy loading with appropriate fallback UI.

The implementation correctly:

  • Wraps with AiFeatureRequest to trigger AI feature loading
  • Provides a visually consistent fallback button during lazy load
  • Passes isLoading to keep the button disabled while config data is fetching

Note that configId will be undefined while isLoading is true, so SendToAiButton should handle the undefined case gracefully (which isLoading already guards against).

src/components/ui/popover.tsx (1)

1-31: LGTM! Standard shadcn/ui Popover implementation.

This is a clean implementation of the Popover component following shadcn/ui conventions. The component properly wraps Radix UI primitives with:

  • Proper ref forwarding via forwardRef
  • Sensible defaults (align="center", sideOffset=4)
  • Portal usage for correct z-index layering
  • Comprehensive styling with animations and accessibility features
  • TypeScript types and displayName for debugging

Based on learnings, maintaining this in its original form ensures upstream consistency and simplifies future upgrades.

src/components/ui/hover-card.tsx (1)

1-27: LGTM! Standard shadcn/ui HoverCard implementation.

This is a proper implementation of the HoverCard component following shadcn/ui conventions. The component correctly wraps Radix UI primitives with:

  • Proper ref forwarding
  • Sensible defaults (align="center", sideOffset=4)
  • Comprehensive styling with animations
  • TypeScript types and displayName

Note that unlike Popover, HoverCard intentionally does not use Portal, which is the standard shadcn/ui pattern. Based on learnings, this should remain in its original form for upstream consistency.

src/components/ui/dialog.tsx (1)

1-120: LGTM! Comprehensive shadcn/ui Dialog implementation.

This is a complete Dialog implementation following shadcn/ui conventions. The component suite includes all standard parts with proper patterns:

  • Full component composition (Overlay, Content, Header, Footer, Title, Description)
  • Portal usage for correct z-index layering
  • Integrated close button with accessibility (sr-only text for screen readers)
  • Proper ref forwarding on all forwardRef components
  • Comprehensive animations and styling
  • TypeScript types and displayName on all components

Based on learnings, this should remain in its original form to maintain upstream consistency and simplify future upgrades.

src/components/ui/command.tsx (1)

1-153: LGTM! Complete shadcn/ui Command palette implementation.

This is a comprehensive Command component implementation following shadcn/ui conventions. The component suite provides:

  • Full command palette composition (Input, List, Empty, Group, Item, Separator, Shortcut)
  • CommandDialog integration with Radix Dialog for modal usage
  • "use client" directive for proper Next.js client-side rendering
  • Search icon integration with CommandInput
  • Proper ref forwarding on all forwardRef components
  • Comprehensive styling for keyboard navigation and selection states
  • TypeScript types and displayName on all components

Based on learnings, maintaining this in its original form ensures upstream consistency and simplifies future upgrades.

src/components/ui/button-group.tsx (2)

24-39: LGTM! Good accessibility implementation.

The ButtonGroup component correctly implements the group role for accessibility and properly forwards props and className. The type definition is clean and the variant integration is appropriate.


64-80: Verify the intentional orientation defaults and important flag.

A few observations to confirm:

  1. Orientation default mismatch: ButtonGroup defaults to "horizontal" (line 19) while ButtonGroupSeparator defaults to "vertical" (line 66). This is likely intentional—vertical separators are typically used within horizontal button groups—but please confirm.

  2. Important flag usage: The !m-0 on line 74 uses the !important flag, which suggests overriding existing Separator margins. Verify this is necessary and cannot be resolved through composition or more specific selectors.

src/ui/Layout/SearchLayout.tsx (1)

16-27: LGTM on lazy loading pattern.

The dynamic imports with React.lazy are well-structured for code splitting, ensuring the AI SDK is only bundled when first accessed.

pages/_app.tsx (1)

16-20: Clean provider integration.

The AiChatPopoverProvider wrapper ensures AI chat context is available throughout the app, regardless of the authentication system in use. This is a clean pattern for global state management.

src/components/ai/AiChat.tsx (3)

230-233: Verify intent: completed reasoning is hidden.

The renderReasoningPart function returns null when reasoningPart.state !== "streaming", which means completed reasoning panels are not shown. If this is intentional (to reduce visual clutter after streaming ends), consider adding a comment. Otherwise, users may want to see the reasoning after it completes.


69-92: Well-implemented type guard.

The isPlotTimeseriesOutput function provides thorough runtime validation with proper null checks, type narrowing, and array element validation.


160-175: Good pattern for tool approval with follow-up.

The handleToolApproval correctly triggers a follow-up message when the chat is idle (not streaming/submitted), ensuring the LLM continues processing after user approval.

src/components/ai-elements/reasoning.tsx (1)

1-187: AI SDK registry component - no modifications suggested.

This component is from the AI SDK registry. Per project conventions, these components are kept in their original form to maintain upstream compatibility and simplify future upgrades.

Based on learnings, modifications are not recommended.

src/components/ai-elements/code-block.tsx (1)

109-118: Static analysis note: dangerouslySetInnerHTML is acceptable here.

The static analysis flagged XSS risk, but this is a false positive. The HTML is generated by Shiki's codeToHtml function from the code prop, not from untrusted user input. Shiki produces safe, syntax-highlighted HTML.

Per project conventions, this AI SDK registry component is kept unmodified.

Based on learnings, no modifications suggested.

src/components/ai-elements/conversation.tsx (1)

1-100: AI SDK registry component approved as-is.

This component follows the project's convention of keeping AI SDK registry components unmodified. The StickToBottom integration and conditional scroll button rendering are well-structured.

Based on learnings, no modifications suggested.

src/components/ai-elements/tool.tsx (1)

79-82: Previous issues noted; registry component guidance applies.

The edge cases identified in earlier reviews (displayTitle fallback for types without dashes, and hardcoded JSON language for string outputs) remain. However, based on learnings, these AI SDK registry components should generally not be modified to maintain upstream compatibility. If these issues impact your use case, consider wrapping or composing rather than direct edits.

Based on learnings, AI SDK registry components should avoid modifications to simplify upgrades.

Also applies to: 146-152

src/components/ai-elements/message.tsx (1)

194-198: Previous reviews identified several issues; registry component guidance applies.

Earlier reviews flagged important concerns:

  • Lines 194-198: useEffect dependency causing potential infinite re-renders
  • Lines 309-320: shallow equality in memo comparison
  • Lines 330-406: missing URL validation for attachments
  • Lines 377-386: missing TooltipProvider wrapper inconsistent with MessageAction pattern

These are AI SDK registry components. Per learnings, avoid direct modifications to maintain upstream compatibility and simplify upgrades. If addressing these issues is necessary, prefer wrapping or composition over editing the registry code directly.

Based on learnings, AI SDK registry components should minimize modifications for easier upgrades.

Also applies to: 309-320, 330-406, 377-386

Copy link

@coderabbitai coderabbitai bot left a comment

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 (3)
src/lib/timeseries.ts (2)

1-4: Consider exporting the ParsedTimestamp type.

The type is used as the return type for parseTimestamp, so exporting it would allow consumers to properly type variables that hold the result.

-type ParsedTimestamp = {
+export type ParsedTimestamp = {
   numericValue: number;
   label: string;
 };

15-23: Document the heuristic's limitations.

The value < 1e11 threshold works well for modern timestamps but will incorrectly multiply millisecond timestamps from before March 1973 by 1000. If historical data is possible, consider adding a brief comment noting this edge case.

src/components/ai/AiChat.tsx (1)

155-169: Consider adaptive date formatting for varied time ranges.

The tooltip always includes hour and minute formatting. For timeseries spanning days, months, or years, you might want adaptive formatting that shows only the relevant granularity (e.g., "Jan 2024" for yearly spans vs "10:30 AM" for hourly spans).

💡 Optional adaptive formatting example
             content={
               <ChartTooltipContent
                 indicator="line"
                 labelFormatter={(value) => {
                   const date = new Date(value);
+                  const span = timeRange?.span ?? 0;
+                  // Adaptive formatting based on time range span
+                  if (span > 365 * 24 * 60 * 60 * 1000) {
+                    // Years
+                    return date.toLocaleDateString("en-US", { year: "numeric", month: "short" });
+                  } else if (span > 7 * 24 * 60 * 60 * 1000) {
+                    // Weeks/months
+                    return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
+                  }
+                  // Hours/days - include time
                   return date.toLocaleDateString("en-US", {
+                    month: "short",
+                    day: "numeric",
                     hour: "2-digit",
                     minute: "2-digit"
                   });
                 }}
               />
             }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a8cb85 and 6a7ecd7.

📒 Files selected for processing (3)
  • src/components/ai/AiChat.tsx
  • src/lib/timeseries.ts
  • src/pages/audit-report/components/View/panels/TimeseriesPanel.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-20T08:54:29.416Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ui/progress.tsx:0-0
Timestamp: 2025-12-20T08:54:29.416Z
Learning: The project uses shadcn/ui components. These components are kept in their original form without modifications, even if potential improvements are identified. This helps maintain consistency with upstream and simplifies future upgrades.

Applied to files:

  • src/components/ai/AiChat.tsx
📚 Learning: 2025-12-20T08:56:21.836Z
Learnt from: adityathebe
Repo: flanksource/flanksource-ui PR: 2791
File: src/components/ai-elements/prompt-input.tsx:1130-1187
Timestamp: 2025-12-20T08:56:21.836Z
Learning: AI SDK components from the registry (ai-elements: https://registry.ai-sdk.dev/) are kept in their original form without modifications, similar to shadcn/ui components. This helps maintain consistency with upstream and simplifies future upgrades.

Applied to files:

  • src/components/ai/AiChat.tsx
🔇 Additional comments (9)
src/lib/timeseries.ts (1)

52-85: LGTM!

The formatTick function correctly handles edge cases (non-finite values, null timeRange) and uses sensible thresholds for adaptive formatting based on the time span.

src/pages/audit-report/components/View/panels/TimeseriesPanel.tsx (1)

20-20: LGTM!

Good refactor centralizing timeseries utilities into a shared module, improving consistency and reducing code duplication across the codebase.

src/components/ai/AiChat.tsx (7)

1-66: LGTM! Clean imports and well-defined types.

The type definitions for timeseries data are clear and properly structured with the discriminated union pattern using the kind field.


68-91: LGTM! Thorough type guard implementation.

The type guard properly validates the entire structure including nested array elements, providing good runtime safety for the plot data.


194-274: LGTM! Well-structured handlers with proper error management.

The handlers correctly manage state transitions, error clearing, and tool approval workflows. The handleToolApproval logic properly checks whether to trigger a follow-up sendMessage based on the current status.


276-300: Verify: Reasoning only displayed while streaming.

The renderReasoningPart function returns null when reasoningPart.state !== "streaming" (line 286-288), which means completed reasoning is hidden from users. This might be intentional design, but please confirm whether users should be able to review the AI's reasoning after it completes, or if it should only be visible during the streaming phase.


302-424: LGTM! Solid rendering logic for tools and messages.

The rendering helpers properly handle different part types, tool approval flows, custom outputs (like timeseries charts), and message content. The tool rendering includes comprehensive state management for approvals, inputs, outputs, and errors.


426-545: LGTM! Well-structured layout with comprehensive state handling.

The main render properly handles all states (empty, loading, streaming, error) with appropriate UI feedback. Good accessibility with aria-labels on interactive elements, and proper conditional rendering for optional features like quick prompts and action buttons.


93-127: Custom timeseries utilities are properly implemented.

The parseTimestamp and formatTick utilities from lib/timeseries handle edge cases well. formatTick includes adaptive formatting that adjusts based on time range span (date-only for spans ≥7 days, date+time for ≥1 day, time-only otherwise), and parseTimestamp correctly falls back to using the index when timestamps are null or undefined. The data transformation in PlotTimeseriesChart is solid.

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.

3 participants