Skip to content

feat(ai): AI Chat UI improvements#2883

Open
adityathebe wants to merge 5 commits intomainfrom
feat/ai-chat-ui-improvements
Open

feat(ai): AI Chat UI improvements#2883
adityathebe wants to merge 5 commits intomainfrom
feat/ai-chat-ui-improvements

Conversation

@adityathebe
Copy link
Member

@adityathebe adityathebe commented Feb 23, 2026

  • Show tool call args summary in tool header title (truncated at 90 chars)
  • Unwrap MCP protocol envelope in tool output: extract inner text, render JSON results with syntax highlighting, plain text as-is
image image image

Closes #2880

Summary by CodeRabbit

  • New Features

    • Tool titles now include a compact summary of input arguments.
    • Configuration link shown in tool details when available.
  • Improvements

    • Tool output rendering enhanced: JSON pretty-printed, plain text shown in monospace, and error/results header simplified.
  • UI Changes

    • “Output truncated” badge and icon removed; header renders as a simple title.

@vercel
Copy link

vercel bot commented Feb 23, 2026

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

Project Deployment Actions Updated (UTC)
aws-preview Ready Ready Preview Feb 24, 2026 8:02pm
flanksource-ui Ready Ready Preview Feb 24, 2026 8:02pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4733574 and f976d8c.

📒 Files selected for processing (2)
  • app/api/chat/tools.ts
  • src/components/ai/mcp-utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/ai/mcp-utils.ts

Walkthrough

Adds a standalone ToolOutput component and mcp-utils helpers; AiChat now appends compact tool-arg summaries to tool titles and conditionally renders a ConfigLink when an input has a string config_id. Removes the previous truncation badge from the old tool UI.

Changes

Cohort / File(s) Summary
Tool output component
src/components/ai/ToolOutput.tsx
New exported ToolOutput component that unwraps MCP envelopes, detects truncation, attempts JSON parsing, and renders output as pretty JSON, preformatted text, or fallback nodes; renders "Error" vs "Result" header and truncation badge when applicable.
MCP utilities
src/components/ai/mcp-utils.ts
Adds TRUNCATION_MARKER, isToolOutputTruncated, unwrapMCPEnvelope, and tryParseJSON helpers used by ToolOutput and other code.
AiChat UI
src/components/ai/AiChat.tsx
Adds formatToolArgsSummary(input, maxLen) to serialize tool inputs into a compact title suffix, extracts config_id from tool input and conditionally renders a ConfigLink beneath the tool header, and imports ToolOutput from the new module.
Old tool UI adjustments
src/components/ai-elements/tool.tsx
Removes AlertTriangleIcon import, deletes local truncation helpers and truncated-flag rendering, simplifies header rendering (drops badge column).
Server-side marker import
app/api/chat/tools.ts
Replaces in-file TRUNCATION_MARKER export with an import from @flanksource-ui/components/ai/mcp-utils.

Possibly related PRs

Suggested reviewers

  • moshloop
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(ai): AI Chat UI improvements' directly aligns with the main objective of improving the AI Chat UI by adding tool call args summary, handling config links, and unwrapping MCP protocol envelopes.
Linked Issues check ✅ Passed All three coding requirements from issue #2880 are implemented: tool call args summary in headers with truncation, config_id conversion to clickable links, and MCP protocol envelope unwrapping with JSON rendering.
Out of Scope Changes check ✅ Passed All changes directly support the linked issue objectives; no extraneous modifications detected. The refactoring of TRUNCATION_MARKER is a necessary supporting change for the main features.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/ai-chat-ui-improvements

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.

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 (2)
src/components/ai/AiChat.tsx (1)

331-356: Keep tool‑args summaries single‑line.

Values can include newlines (e.g., multiline prompts), which may expand the header despite truncation. Normalizing whitespace keeps the title compact.

🧼 Optional tweak
-    if (!result) return "";
-    return result.length > maxLen
-      ? result.substring(0, maxLen - 1) + "…"
-      : result;
+    const normalized = result.replace(/\s+/g, " ").trim();
+    if (!normalized) return "";
+    return normalized.length > maxLen
+      ? normalized.substring(0, maxLen - 1) + "…"
+      : normalized;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ai/AiChat.tsx` around lines 331 - 356, The tool-args summary
may contain newlines which expand the header; inside formatToolArgsSummary
normalize whitespace for all serialized values (replace sequences of whitespace
including newlines with a single space and trim) before joining/truncating.
Update the serialize function and the main mapping in formatToolArgsSummary so
each String(v) is passed through a whitespace-collapse/trim step (e.g.,
replace(/\s+/g,' ') and .trim()) so entries like multiline prompts become
single-line, then apply the existing maxLen truncation logic.
src/components/ai-elements/tool.tsx (1)

205-211: Remove redundant string fallback after resolved handling.

After resolving, string outputs are already handled, so the final else if (typeof output === "string") branch is unreachable and can be removed for clarity.

🧹 Cleanup
-  } else if (typeof output === "string") {
-    Output = <CodeBlock code={output} language="json" />;
-  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ai-elements/tool.tsx` around lines 205 - 211, The final
else-if branch that checks typeof output === "string" is redundant because
strings are already handled by the resolved branch; remove the unreachable
branch that returns <CodeBlock code={output} language="json" />. Locate the
conditional block using the identifiers resolved, isValidElement, Output and
CodeBlock in the component (the branch that currently does: } else if (typeof
resolved === "object" && !isValidElement(resolved)) { ... } else if (typeof
output === "string") { ... }) and delete the trailing else-if so the control
flow ends after the resolved/object handling, keeping all other branches intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/ai-elements/tool.tsx`:
- Around line 144-172: The MCP envelope parsing (unwrapMCPEnvelope) and JSON
helper (tryParseJSON) must be removed from src/components/ai-elements/tool.tsx
and relocated into a wrapper or extension module that sits outside the
ai-elements registry files; create a new local wrapper component or utility
(e.g., aiEnvelopeWrapper or mcpUtils) and move the logic for unwrapMCPEnvelope
and tryParseJSON there, update any callers to import from that wrapper, and
leave tool.tsx unchanged so registry files remain pristine.
- Around line 184-189: The truncation badge check uses the raw output instead of
the MCP-unwrapped text, so move the isTruncated call to use the resolved string:
compute mcpText = unwrapMCPEnvelope(output), resolved = mcpText ?? output, then
call isTruncated(resolved) (update the truncated constant) so the badge will
detect markers inside MCP content; adjust any downstream uses of truncated to
the new value.

---

Nitpick comments:
In `@src/components/ai-elements/tool.tsx`:
- Around line 205-211: The final else-if branch that checks typeof output ===
"string" is redundant because strings are already handled by the resolved
branch; remove the unreachable branch that returns <CodeBlock code={output}
language="json" />. Locate the conditional block using the identifiers resolved,
isValidElement, Output and CodeBlock in the component (the branch that currently
does: } else if (typeof resolved === "object" && !isValidElement(resolved)) {
... } else if (typeof output === "string") { ... }) and delete the trailing
else-if so the control flow ends after the resolved/object handling, keeping all
other branches intact.

In `@src/components/ai/AiChat.tsx`:
- Around line 331-356: The tool-args summary may contain newlines which expand
the header; inside formatToolArgsSummary normalize whitespace for all serialized
values (replace sequences of whitespace including newlines with a single space
and trim) before joining/truncating. Update the serialize function and the main
mapping in formatToolArgsSummary so each String(v) is passed through a
whitespace-collapse/trim step (e.g., replace(/\s+/g,' ') and .trim()) so entries
like multiline prompts become single-line, then apply the existing maxLen
truncation logic.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e45c64 and 96f882a.

📒 Files selected for processing (2)
  • src/components/ai-elements/tool.tsx
  • src/components/ai/AiChat.tsx

Keep ai-elements/tool.tsx free of local MCP parsing logic by introducing a local ToolOutput wrapper and mcp utilities.

Also compute truncation badge state from MCP-unwrapped text so truncated markers inside content.text are detected.
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: 3

♻️ Duplicate comments (1)
src/components/ai-elements/tool.tsx (1)

156-158: Registry file still modified — move the badge removal to the wrapper instead.

Even though the MCP parsing has been correctly extracted to mcp-utils.ts/ToolOutput.tsx, this file is still being touched. The truncation badge was removed directly from the registry's ToolOutput rather than being omitted only in the wrapper. Keeping tool.tsx pristine means the wrapper (src/components/ai/ToolOutput.tsx) should simply never use the registry's ToolOutput component for rendering — which is already the case — so there's no need to alter this file at all.

Based on learnings, do not edit components under src/components/ai-elements/ that come from the AI SDK registry (https://registry.ai-sdk.dev/); changes should be applied via extension/wrapper components that sit outside the ai-elements directory.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ai-elements/tool.tsx` around lines 156 - 158, Revert any edits
in the registry-sourced component src/components/ai-elements/tool.tsx (e.g.,
restore the original header/behavior around the {errorText ? "Error" : "Result"}
line) and instead implement the truncation-badge removal in the wrapper
component src/components/ai/ToolOutput.tsx so the wrapper never renders the
registry's ToolOutput with the badge; ensure the wrapper filters/omits the
truncation badge before passing props or rendering the registry ToolOutput
component.
🧹 Nitpick comments (3)
src/components/ai/ToolOutput.tsx (1)

29-31: isError: true in the MCP envelope is silently ignored.

isError: true in the MCP spec signals to the client that the tool attempted execution but failed. unwrapMCPEnvelope extracts the content text regardless of this flag, so an error response from an MCP tool will be labelled "Result" rather than "Error" (since part.errorText from the AI SDK is unrelated to the envelope's isError field). Consider propagating isError so the calling component can decide how to label and style the output. This is out of scope for the current PR but worth tracking as a follow-up.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ai/ToolOutput.tsx` around lines 29 - 31, The MCP envelope's
isError flag is being dropped by unwrapMCPEnvelope, so ToolOutput.tsx (variables
mcpText and resolved) can't tell when an MCP tool failed; update
unwrapMCPEnvelope to return both the extracted text and the envelope's isError
(e.g., an object { text, isError }) and change callers in ToolOutput.tsx to use
that result instead of just mcpText/resolved so the component can style/label
output as an Error when isError is true; ensure isToolOutputTruncated continues
to operate on the extracted text.
src/components/ai/AiChat.tsx (1)

331-356: Move formatToolArgsSummary to module level.

The function closes over nothing — no props, state, or refs — so defining it inside AIChat causes unnecessary recreation on every render. Hoisting it to module scope is a zero-risk improvement.

♻️ Proposed refactor
-  const formatToolArgsSummary = (input: unknown, maxLen = 90): string => {
+// Module-level utility — no component dependencies
+function formatToolArgsSummary(input: unknown, maxLen = 90): string {
     if (!input || typeof input !== "object" || Array.isArray(input)) {
       return "";
     }
     ...
-  };
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ai/AiChat.tsx` around lines 331 - 356, Hoist the
formatToolArgsSummary function out of the AIChat component to module scope: cut
the entire function (including its serialize helper) and paste it at top-level
in the same file so it is defined once per module rather than on every render;
keep the same signature (input: unknown, maxLen = 90): string and behavior,
ensure it still references no component props/state, and update any internal
references inside AIChat to use the now top-level formatToolArgsSummary
identifier (no other API changes are required).
src/components/ai/mcp-utils.ts (1)

32-46: tryParseJSON return type unknown | null simplifies to unknown.

In TypeScript, null is assignable to unknown, making unknown | null equivalent to unknown. Callers already narrow with if (parsed !== null) so the intent is clear, but the signature can be slightly more precise. This is a nitpick.

♻️ Optional tightening
-export function tryParseJSON(text: string): unknown | null {
+export function tryParseJSON(text: string): object | unknown[] | null {

Or keep the | null as-is for documentation clarity — either is acceptable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ai/mcp-utils.ts` around lines 32 - 46, The return type of
tryParseJSON is declared as "unknown | null" which is redundant because null is
assignable to unknown; update the function signature "export function
tryParseJSON(text: string): unknown | null" to "export function
tryParseJSON(text: string): unknown" (keeping the implementation the same so it
still returns null on parse failure) — this tightens the type while preserving
behavior; the function to change is tryParseJSON.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/ai/AiChat.tsx`:
- Around line 347-354: The top-level kv-pair join in the AiChat result builder
uses a plain space which makes pairs run together; change the join separator
from " " to ", " so pairs render as "arg1: val, arg2: val". Also update the
nested serialize path to use ", " (comma plus space) instead of "," to keep
consistent spacing for nested lists/arrays; adjust nothing else except
preserving the truncation logic using maxLen and the ellipsis when result
exceeds maxLen. Ensure you modify the code that constructs result (the
Object.entries(...).map(...).join(...)) and the serialize(...) implementation so
both use ", " as the comma separator.

In `@src/components/ai/mcp-utils.ts`:
- Around line 1-9: The local TRUNCATION_MARKER constant in this file duplicates
the same literal used server-side and risks silent drift; extract the constant
into a shared client-safe module (e.g., create src/lib/ai-constants.ts exporting
TRUNCATION_MARKER) and update isToolOutputTruncated to import TRUNCATION_MARKER
from that shared module (also update app/api/chat/tools.ts to import the same
shared constant) so both client and server reference the single source of truth.

In `@src/components/ai/ToolOutput.tsx`:
- Around line 29-53: The code treats null as an object and ends up
JSON-stringifying null; update the rendering guard so the object branch only
runs when resolved is not null (e.g., check resolved !== null or resolved !=
null) before evaluating typeof resolved === "object" &&
!isValidElement(resolved); locate this logic around unwrapMCPEnvelope/output
handling and adjust the condition used to assign Output (in the block that
currently uses tryParseJSON, CodeBlock, and isValidElement) so null values fall
through to the intended empty/early-return behavior instead of rendering "null".

---

Duplicate comments:
In `@src/components/ai-elements/tool.tsx`:
- Around line 156-158: Revert any edits in the registry-sourced component
src/components/ai-elements/tool.tsx (e.g., restore the original header/behavior
around the {errorText ? "Error" : "Result"} line) and instead implement the
truncation-badge removal in the wrapper component
src/components/ai/ToolOutput.tsx so the wrapper never renders the registry's
ToolOutput with the badge; ensure the wrapper filters/omits the truncation badge
before passing props or rendering the registry ToolOutput component.

---

Nitpick comments:
In `@src/components/ai/AiChat.tsx`:
- Around line 331-356: Hoist the formatToolArgsSummary function out of the
AIChat component to module scope: cut the entire function (including its
serialize helper) and paste it at top-level in the same file so it is defined
once per module rather than on every render; keep the same signature (input:
unknown, maxLen = 90): string and behavior, ensure it still references no
component props/state, and update any internal references inside AIChat to use
the now top-level formatToolArgsSummary identifier (no other API changes are
required).

In `@src/components/ai/mcp-utils.ts`:
- Around line 32-46: The return type of tryParseJSON is declared as "unknown |
null" which is redundant because null is assignable to unknown; update the
function signature "export function tryParseJSON(text: string): unknown | null"
to "export function tryParseJSON(text: string): unknown" (keeping the
implementation the same so it still returns null on parse failure) — this
tightens the type while preserving behavior; the function to change is
tryParseJSON.

In `@src/components/ai/ToolOutput.tsx`:
- Around line 29-31: The MCP envelope's isError flag is being dropped by
unwrapMCPEnvelope, so ToolOutput.tsx (variables mcpText and resolved) can't tell
when an MCP tool failed; update unwrapMCPEnvelope to return both the extracted
text and the envelope's isError (e.g., an object { text, isError }) and change
callers in ToolOutput.tsx to use that result instead of just mcpText/resolved so
the component can style/label output as an Error when isError is true; ensure
isToolOutputTruncated continues to operate on the extracted text.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96f882a and 4733574.

📒 Files selected for processing (4)
  • src/components/ai-elements/tool.tsx
  • src/components/ai/AiChat.tsx
  • src/components/ai/ToolOutput.tsx
  • src/components/ai/mcp-utils.ts

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.

AI Chat - UI Improvements

1 participant