Skip to content

🧩 feat: Redesign Tool Call UI with Contextual Icons, Smart Grouping, and Rich Output Rendering#12163

Merged
danny-avila merged 77 commits into
devfrom
feat/plugins-ui
Mar 25, 2026
Merged

🧩 feat: Redesign Tool Call UI with Contextual Icons, Smart Grouping, and Rich Output Rendering#12163
danny-avila merged 77 commits into
devfrom
feat/plugins-ui

Conversation

@berry-13

@berry-13 berry-13 commented Mar 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Overhauls every tool-call rendering surface in the chat UI. Replaces the generic spinner/checkmark pattern with type specific icons, groups consecutive tool calls into collapsible sections, and adds intelligent output rendering with error handling, JSON parsing, and copy support.

What changed:

  • Tool calls now show context-aware icons (terminal for code, globe for web search, plug for MCP, etc.) instead of a generic spinner and purple checkmark
  • 2+ consecutive tool calls are automatically grouped into a collapsible "Used N tools" section with stacked icons and friendly labels
  • Tool output is rendered first with intelligent JSON parsing, error cleaning, truncation, and copy support; parameters are collapsible below
  • useExpandCollapse hook replaces manual height animation logic across all expandable tool surfaces
  • WebSearch shows inline source favicons with a collapsible source list
  • RetrievalCall shows file sources with type icons, relevance scores, and a file preview dialog
  • ImageGen unified to handle both legacy and agent-style tool calls
  • AgentHandoff uses a semantic instead of a clickable
  • CodeBlock split into smaller components; Mermaid split from a single 850-line file into a proper module
  • LangIcon component maps ~45 languages to brand icons for code block headers
  • Light mode syntax highlighting fixed (was hardcoded for dark backgrounds)
  • showCode setting renamed to autoExpandTools, scope expanded to all tool types, default changed to off
  • Keyboard and screen reader accessibility throughout: aria-live regions, inert on collapsed content, aria-expanded, focus-visible rings

New dependency: @icons-pack/react-simple-icons

Change Type

  • Style
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Testing

Verified all tool call rendering paths by exercising each tool type in the chat UI and confirming correct icon, grouping, expand/collapse, output rendering, and accessibility behavior.

Test Configuration

  • Node.js: v20.19.0+
  • Browser: Chromium (latest) / Firefox Dev Edition (latest)

Manual test cases

  1. Tool call icons: Send messages that trigger execute_code, web_search, image_gen_oai, file_search, an MCP tool, and a generic tool. Verify each renders its specific icon (terminal, globe,
    image, file-search, plug, wrench). MCP tools with a configured icon URL should show the custom icon.
  2. Tool call grouping: Trigger 2+ tool calls in a single response (e.g., web search followed by code execution). Verify they collapse into a "Used N tools" section with stacked icons and
    friendly labels. Verify auto-expand during streaming and auto-collapse on completion.
  3. Output rendering: Verify tool output appears above parameters. JSON arrays and objects should render as readable text, not raw JSON. Error outputs should show cleaned messages in red with a
    "Details" expander for the raw error. Long outputs should truncate with "Show more".
  4. Expand/collapse: Toggle tool call details, parameters, agent handoff instructions, and tool groups. Verify smooth animation, no layout shift, and that collapsed content is not
    keyboard-focusable (inert).
  5. WebSearch: Verify inline favicon stack, collapsible source list with individual links, and correct progress text.
  6. RetrievalCall / file_search: Verify file source list with type-specific icons, relevance scores, page numbers, and click-to-preview via FilePreviewDialog.
  7. ImageGen: Test with image_gen_oai, image_edit_oai, and gemini_image_gen. Verify PixelCard during generation, responsive image sizing, and completed state. Test legacy function-type image
    generation tools as well.
  8. AgentHandoff: Verify button is keyboard-navigable (Tab, Enter/Space), shows focus ring, and expands handoff instructions.
  9. Code blocks: Verify CodeBar language label, copy button, run button with success/error animation, floating copy bar on hover, and language brand icons in code headers.
  10. Light/dark theme: Toggle themes and verify syntax highlighting is readable in both. Code blocks should not show white text on light backgrounds.
  11. Auto-expand tools setting: Toggle "Auto-expand tool details" in Settings > Chat. When on, completed tool calls should auto-expand. When off, they should remain collapsed.
  12. Screen reader: Navigate tool calls with a screen reader. Verify aria-live regions announce status changes ("Running...", "Completed"), toggle buttons announce expanded state.

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes
  • Any changes dependent on mine have been merged and published in downstream modules.

Copilot AI review requested due to automatic review settings March 9, 2026 22:36
@berry-13 berry-13 added ✨ enhancement New feature or request 🔨 refactor Code cleanup without new features 🎨 design UI/UX improvements labels Mar 9, 2026
@berry-13
berry-13 changed the base branch from main to dev March 9, 2026 22:36
@berry-13
berry-13 changed the base branch from dev to dev-staging March 9, 2026 22:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR modernizes and restyles the client “tools” experience in chat messages, including code blocks, web search, Mermaid diagrams, and tool-call output rendering/grouping.

Changes:

  • Introduces grouped rendering for sequential tool calls and new “tool output” UI primitives (icons, stacked icons, output formatting).
  • Refactors Mermaid rendering into a dedicated module with dialog + zoom controls and more robust SVG processing.
  • Updates code block UI with a shared copy button/hook, language icons, and a floating action bar; restyles multiple message/tool components.

Reviewed changes

Copilot reviewed 49 out of 50 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
package-lock.json Adds icon dependency lock entry (includes new engines/resolution metadata).
client/package.json Adds @icons-pack/react-simple-icons dependency.
client/src/utils/index.ts Re-exports new groupToolCalls utility.
client/src/utils/groupToolCalls.ts Adds sequential tool-call grouping utility.
client/src/locales/en/translation.json Adds new UI strings for tool/source/output UI.
client/src/hooks/Messages/useExpandCollapse.ts Adds shared expand/collapse transition hook.
client/src/hooks/Messages/index.ts Exposes useExpandCollapse + transition constant.
client/src/hooks/MCP/useMCPIconMap.ts Adds hook mapping MCP server → iconPath.
client/src/hooks/MCP/index.ts Exports useMCPIconMap.
client/src/components/Web/SourceHovercard.tsx Simplifies favicon rendering + lazy loading.
client/src/components/Messages/Content/useCopyCode.ts Adds reusable copy-to-clipboard hook for code blocks.
client/src/components/Messages/Content/RunCode.tsx Restyles run-code button.
client/src/components/Messages/Content/ResultSwitcher.tsx Restyles result navigation UI.
client/src/components/Messages/Content/MermaidHeader.tsx Removes old Mermaid header (moved into module).
client/src/components/Messages/Content/Mermaid/useSvgProcessing.ts Adds SVG parsing/fixups + blob URL generation for Mermaid.
client/src/components/Messages/Content/Mermaid/useMermaidZoom.ts Adds reusable zoom/pan logic for Mermaid.
client/src/components/Messages/Content/Mermaid/index.ts Exports Mermaid module entrypoints.
client/src/components/Messages/Content/Mermaid/ZoomControls.tsx Adds zoom/reset/copy controls for Mermaid.
client/src/components/Messages/Content/Mermaid/MermaidHeader.tsx Adds new Mermaid header (expand/toggle/copy).
client/src/components/Messages/Content/Mermaid/MermaidErrorBoundary.tsx Restyles Mermaid error fallback UI.
client/src/components/Messages/Content/Mermaid/MermaidDialog.tsx Adds expanded Mermaid dialog with zoom/pan.
client/src/components/Messages/Content/Mermaid/Mermaid.tsx Adds new Mermaid renderer implementation.
client/src/components/Messages/Content/Mermaid.tsx Removes old monolithic Mermaid component.
client/src/components/Messages/Content/LangIcon.tsx Adds language→icon mapping using react-simple-icons.
client/src/components/Messages/Content/FloatingCodeBar.tsx Adds floating code actions (run/copy) UI.
client/src/components/Messages/Content/CopyCodeButton.tsx Adds shared copy button component.
client/src/components/Messages/Content/CodeBlock.tsx Refactors code block UI to use new components/bars and tool output section styling.
client/src/components/Messages/Content/CodeBar.tsx Adds inline code header bar with language icon + copy/run actions.
client/src/components/Chat/Messages/Content/WebSearch.tsx Reworks web search progress/completion UI with expandable source list.
client/src/components/Chat/Messages/Content/ToolOutput/index.ts Adds exports for new tool output UI primitives.
client/src/components/Chat/Messages/Content/ToolOutput/ToolIcon.tsx Adds tool icon classification + MCP icon support.
client/src/components/Chat/Messages/Content/ToolOutput/StackedToolIcons.tsx Adds stacked icon renderer for tool summaries.
client/src/components/Chat/Messages/Content/ToolOutput/OutputRenderer.tsx Adds output formatting (JSON/error cleanup/truncation).
client/src/components/Chat/Messages/Content/ToolCallRenderers.ts Adds tool-call renderer classification helper.
client/src/components/Chat/Messages/Content/ToolCallInfo.tsx Refactors tool-call details UI (output renderer + parameter toggling + UI resources).
client/src/components/Chat/Messages/Content/ToolCallGroup.tsx Adds grouped tool-call container with auto-collapse + icon summary.
client/src/components/Chat/Messages/Content/ToolCall.tsx Refactors tool-call row UI (icons, expand animation, error formatting).
client/src/components/Chat/Messages/Content/SearchContent.tsx Removes legacy Sources component usage.
client/src/components/Chat/Messages/Content/RetrievalCall.tsx Updates retrieval call UI to use new tool icons.
client/src/components/Chat/Messages/Content/ProgressText.tsx Updates progress text component API and styling (adds icon/subtitle/error suffix).
client/src/components/Chat/Messages/Content/Parts/Thinking.tsx Restyles thinking UI and adopts shared expand/collapse.
client/src/components/Chat/Messages/Content/Parts/Stdout.tsx Simplifies stdout rendering and styling.
client/src/components/Chat/Messages/Content/Parts/Reasoning.tsx Adopts shared expand/collapse styling.
client/src/components/Chat/Messages/Content/Parts/ExecuteCode.tsx Refactors execute-code details UI with shared expand/collapse and new code window header.
client/src/components/Chat/Messages/Content/Parts/CodeWindowHeader.tsx Adds new header UI for code window (filename/line count/copy).
client/src/components/Chat/Messages/Content/Part.tsx Refactors tool-call routing/typing and integrates updated WebSearch usage.
client/src/components/Chat/Messages/Content/MarkdownComponents.tsx Updates Mermaid imports to new module structure.
client/src/components/Chat/Messages/Content/FinishedIcon.tsx Removes legacy finished icon component.
client/src/components/Chat/Messages/Content/ContentParts.tsx Integrates sequential tool-call grouping and tool-call group rendering.
client/src/components/Chat/Messages/Content/AgentHandoff.tsx Restyles handoff UI and adopts shared expand/collapse + keyboard accessibility.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +102 to +115
const hasParams = useMemo(() => {
if (!input || input.trim().length === 0) {
return false;
}
try {
return JSON.stringify(JSON.parse(text), null, 2);
const parsed = JSON.parse(input);
if (typeof parsed === 'object' && parsed !== null) {
return Object.keys(parsed).length > 0;
}
} catch {
return text;
// Not JSON
}
};

let title =
domain != null && domain
? localize('com_assistants_domain_info', { 0: domain })
: localize('com_assistants_function_use', { 0: function_name });
if (pendingAuth === true) {
title =
domain != null && domain
? localize('com_assistants_action_attempt', { 0: domain })
: localize('com_assistants_attempt_info');
}
return input.trim().length > 0;
}, [input]);

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

hasParams returns true for any non-empty input even when JSON parsing fails, but InputRenderer returns null on non-JSON. This produces a “Parameters” toggle that expands to an empty area. Either treat non-JSON input as hasParams=false, or render a fallback (e.g., a pre/code block) when JSON parsing fails so users can still inspect tool args.

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +53
/**
* Determines which renderer to use for a tool call part.
* Used by grouping logic to identify groupable vs special tool calls.
*/
export function resolveToolRendererType(toolCall: ContentPart): ToolRendererType {
const isStandardToolCall =
'args' in toolCall && (!toolCall.type || toolCall.type === ToolCallTypes.TOOL_CALL);

if (isStandardToolCall) {
const name = ('name' in toolCall ? (toolCall as { name: string }).name : '') ?? '';
if (name === Tools.execute_code || name === Constants.PROGRAMMATIC_TOOL_CALLING) {
return 'execute_code';
}
if (name === 'image_gen_oai' || name === 'image_edit_oai' || name === 'gemini_image_gen') {
return 'openai_image_gen';
}
if (name === Tools.web_search) {
return 'web_search';
}
if (name.startsWith(Constants.LC_TRANSFER_TO_)) {
return 'agent_handoff';
}
return 'generic_tool_call';
}

if ('type' in toolCall) {
if (toolCall.type === ToolCallTypes.CODE_INTERPRETER) {
return 'code_interpreter';
}
if (toolCall.type === ToolCallTypes.RETRIEVAL || toolCall.type === ToolCallTypes.FILE_SEARCH) {
return 'retrieval';
}
if (toolCall.type === ToolCallTypes.FUNCTION) {
return 'function';
}
}

return null;
}

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

This new module appears unused in the client bundle (no imports found for resolveToolRendererType). If it’s meant to be the single source of truth for “special vs groupable” tool calls, please wire it into the grouping logic (e.g., groupToolCalls.ts) and/or the renderer dispatch; otherwise remove it to avoid dead code and duplicated classification logic.

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +61
<button
type="button"
className="rounded-sm p-1 text-text-tertiary transition-colors hover:bg-surface-hover hover:text-text-secondary focus:outline focus:outline-2 focus:outline-border-heavy"
onClick={handleCopy}
aria-label="Copy code"
>

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

The copy button’s aria-label is hard-coded ("Copy code") rather than localized like the rest of the UI strings. Please use localize(...) (and add a translation key if needed) so screen readers get the correct label in non-English locales.

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +35
const CodeBar: React.FC<CodeBarProps> = React.memo(
({ lang, error, codeRef, blockIndex, plugin = null, allowExecution = true }) => {
const { isCopied, handleCopy } = useCopyCode(codeRef);

return (
<div className="flex items-center justify-between px-3 py-1.5 font-sans text-xs text-text-secondary">
<span className="flex items-center gap-1.5 text-xs font-medium">
<LangIcon lang={lang} className="size-3.5" />
{lang}
</span>
{plugin === true ? (
<InfoIcon className="ml-auto flex h-4 w-4 gap-2 text-text-secondary" />
) : (
<div className="flex items-center justify-center gap-2">
{allowExecution === true && (
<RunCode lang={lang} codeRef={codeRef} blockIndex={blockIndex} />
)}
<CopyCodeButton
isCopied={isCopied}
showLabel={error !== true}
className={cn(
'ml-auto flex gap-2 rounded-sm text-text-secondary transition-colors hover:text-text-secondary focus:outline focus:outline-2 focus:outline-border-heavy',
error === true ? 'h-4 w-4 items-start' : '',
)}
onClick={handleCopy}
/>

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

useCopyCode returns buttonRef for focus restoration, but CodeBar doesn’t attach it to the CopyCodeButton ref. As a result wasFocused will never be true and the hook’s focus-management logic won’t work as intended. Pass the returned buttonRef to CopyCodeButton (or simplify useCopyCode so it doesn’t depend on a ref).

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +26
setTimeout(() => {
const focused = document.activeElement as HTMLElement | null;
setIsCopied(false);
requestAnimationFrame(() => focused?.focus());
}, 3000);

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

handleCopy schedules a setTimeout that calls setIsCopied(false) but the timeout isn’t cleared on unmount. If the component unmounts within 3s (e.g., message list virtualization / navigation), this can trigger state updates on an unmounted component. Store the timeout id in a ref and clear it in an effect cleanup (and/or guard with an isMounted flag).

Copilot uses AI. Check for mistakes.
Comment on lines +209 to +229
{allSources.map((source, i) => {
const domain = getCleanDomain(source.link);
return (
<a
key={i}
href={source.link}
target="_blank"
rel="noopener noreferrer"
className={cn(
'flex items-center gap-2.5 px-3 py-2 transition-colors hover:bg-surface-hover',
i > 0 && 'border-t border-border-light',
)}
>
<FaviconImage domain={domain} className="size-4 shrink-0 rounded-sm" />
<span className="min-w-0 flex-1 truncate text-xs font-medium text-text-primary">
{source.title || domain}
</span>
<span className="shrink-0 text-[11px] text-text-secondary">{domain}</span>
</a>
);
})}

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

In the expanded sources list, allSources.map((source, i) => ...) uses the array index as the React key. Since sources are naturally keyed by source.link (and collectSources de-dupes by link), using source.link as the key will avoid unnecessary re-mounts when the list order changes.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +53
import React from 'react';
import SiC from '@icons-pack/react-simple-icons/icons/SiC';
import SiD from '@icons-pack/react-simple-icons/icons/SiD';
import SiR from '@icons-pack/react-simple-icons/icons/SiR';
import SiV from '@icons-pack/react-simple-icons/icons/SiV';
import SiGo from '@icons-pack/react-simple-icons/icons/SiGo';
import SiCss from '@icons-pack/react-simple-icons/icons/SiCss';
import SiLua from '@icons-pack/react-simple-icons/icons/SiLua';
import SiNim from '@icons-pack/react-simple-icons/icons/SiNim';
import SiPhp from '@icons-pack/react-simple-icons/icons/SiPhp';
import SiXml from '@icons-pack/react-simple-icons/icons/SiXml';
import SiZig from '@icons-pack/react-simple-icons/icons/SiZig';
import SiDart from '@icons-pack/react-simple-icons/icons/SiDart';
import SiJson from '@icons-pack/react-simple-icons/icons/SiJson';
import SiLess from '@icons-pack/react-simple-icons/icons/SiLess';
import SiOdin from '@icons-pack/react-simple-icons/icons/SiOdin';
import SiPerl from '@icons-pack/react-simple-icons/icons/SiPerl';
import SiRuby from '@icons-pack/react-simple-icons/icons/SiRuby';
import SiRust from '@icons-pack/react-simple-icons/icons/SiRust';
import SiSass from '@icons-pack/react-simple-icons/icons/SiSass';
import SiToml from '@icons-pack/react-simple-icons/icons/SiToml';
import SiYaml from '@icons-pack/react-simple-icons/icons/SiYaml';
import SiHtml5 from '@icons-pack/react-simple-icons/icons/SiHtml5';
import SiJulia from '@icons-pack/react-simple-icons/icons/SiJulia';
import SiLatex from '@icons-pack/react-simple-icons/icons/SiLatex';
import SiOcaml from '@icons-pack/react-simple-icons/icons/SiOcaml';
import SiScala from '@icons-pack/react-simple-icons/icons/SiScala';
import SiSwift from '@icons-pack/react-simple-icons/icons/SiSwift';
import SiDotnet from '@icons-pack/react-simple-icons/icons/SiDotnet';
import SiElixir from '@icons-pack/react-simple-icons/icons/SiElixir';
import SiErlang from '@icons-pack/react-simple-icons/icons/SiErlang';
import SiFsharp from '@icons-pack/react-simple-icons/icons/SiFsharp';
import SiKotlin from '@icons-pack/react-simple-icons/icons/SiKotlin';
import SiPython from '@icons-pack/react-simple-icons/icons/SiPython';
import SiRacket from '@icons-pack/react-simple-icons/icons/SiRacket';
import SiClojure from '@icons-pack/react-simple-icons/icons/SiClojure';
import SiCrystal from '@icons-pack/react-simple-icons/icons/SiCrystal';
import SiDelphi from '@icons-pack/react-simple-icons/icons/SiDelphi';
import SiFortran from '@icons-pack/react-simple-icons/icons/SiFortran';
import SiGraphql from '@icons-pack/react-simple-icons/icons/SiGraphql';
import SiGnubash from '@icons-pack/react-simple-icons/icons/SiGnubash';
import SiHaskell from '@icons-pack/react-simple-icons/icons/SiHaskell';
import SiOpenjdk from '@icons-pack/react-simple-icons/icons/SiOpenjdk';
import SiMarkdown from '@icons-pack/react-simple-icons/icons/SiMarkdown';
import SiSolidity from '@icons-pack/react-simple-icons/icons/SiSolidity';
import SiCplusplus from '@icons-pack/react-simple-icons/icons/SiCplusplus';
import SiPurescript from '@icons-pack/react-simple-icons/icons/SiPurescript';
import SiTypescript from '@icons-pack/react-simple-icons/icons/SiTypescript';
import SiJavascript from '@icons-pack/react-simple-icons/icons/SiJavascript';
import SiWebassembly from '@icons-pack/react-simple-icons/icons/SiWebassembly';
import SiCoffeescript from '@icons-pack/react-simple-icons/icons/SiCoffeescript';
import SiApachegroovy from '@icons-pack/react-simple-icons/icons/SiApachegroovy';
import type { IconType } from '@icons-pack/react-simple-icons/types';

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

LangIcon statically imports a large number of icon modules. Even with tree-shaking, this can significantly increase the client bundle/chunk size because all these modules become direct dependencies of the code block UI. Consider switching to a lighter-weight approach (e.g., a single icon set component with runtime lookup, or lazy/dynamic importing of just the required icon) to keep initial load size down.

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +52
export function getMCPServerName(toolName: string): string {
if (!toolName.includes(Constants.mcp_delimiter)) {
return '';
}
const [, server] = toolName.split(Constants.mcp_delimiter);
return server || '';
}

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

getMCPServerName uses const [, server] = toolName.split(Constants.mcp_delimiter), which differs from the backend pattern that uses .split(...).pop() to get the server name. Using .pop() (or splitting once at the first delimiter index) would be more robust if the delimiter ever appears more than once in the tool name.

Copilot uses AI. Check for mistakes.
Comment thread client/package.json Outdated
Comment on lines 38 to 39
"@icons-pack/react-simple-icons": "^13.12.0",
"@librechat/client": "*",

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

Adding @icons-pack/react-simple-icons@^13.12.0 looks incompatible with this repo’s supported Node versions: the lockfile entry for 13.12.0 declares engines.node >= 24, while the project docs/Dockerfile target Node 20.x. Please pin/downgrade to a Node-20-compatible version (or use a different icon package) to avoid install/CI breakage.

Copilot uses AI. Check for mistakes.
Comment on lines 76 to 81
const getIcon = () => {
if (error) {
if (error && !errorSuffix) {
return <CancelledIcon />;
}
if (progress < 1) {
return <Spinner />;
}
return <FinishedIcon />;
return iconProp ?? null;
};

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

ProgressText no longer renders any default icon while in-progress/success unless callers pass the new icon prop. Existing call sites (e.g., CodeAnalyze.tsx, ImageGen.tsx) still rely on the previous default spinner/finished icons, so they will now render with no icon. Either restore sensible defaults when icon is undefined (spinner while progress < 1, finished icon when progress >= 1) or update all call sites to pass an icon.

Copilot uses AI. Check for mistakes.
@danny-avila
danny-avila force-pushed the dev-staging branch 5 times, most recently from 5b88468 to 1503612 Compare March 21, 2026 18:31
@berry-13
berry-13 changed the base branch from dev-staging to dev March 21, 2026 23:56
@berry-13
berry-13 marked this pull request as ready for review March 22, 2026 00:13
@github-actions

Copy link
Copy Markdown
Contributor

🚨 Unused i18next Keys Detected

The following translation keys are defined in translation.json but are not used in the codebase:

  • com_ui_api_source
  • com_ui_auth_pending
  • com_ui_extracted_content
  • com_ui_lines_count
  • com_ui_mcp_source
  • com_ui_searched_n_sources

⚠️ Please remove these unused keys to keep the translation files clean.

@berry-13
berry-13 marked this pull request as draft March 22, 2026 00:55
@berry-13 berry-13 changed the title style: tools UI feat: redesign tool call UI with type-specific icons, smart grouping, and rich output rendering Mar 22, 2026
@berry-13
berry-13 marked this pull request as ready for review March 22, 2026 02:22
… and rich output rendering

Replace the generic spinner/checkmark tool call UI with a modern, Cursor-inspired design:

- Add per-tool-type icons (Plug for MCP, Terminal for code, Globe for web search, etc.)
- Group 2+ consecutive tool calls into collapsible "Used N tools" sections
- Stack unique tool icons in grouped headers with overlapping circle design
- Replace raw JSON output with intelligent renderers (table, error, text)
- Restructure ToolCallInfo: output first, parameters collapsible at bottom
- Add shared useExpandCollapse hook for consistent animations
- Add CodeWindowHeader for ExecuteCode windowed view
- Remove FinishedIcon (purple checkmark) entirely
Add useMCPIconMap hook to resolve MCP server names to their configured
icon paths. ToolIcon and StackedToolIcons now accept custom icon URLs,
showing actual server logos (e.g., Home Assistant, GitHub) instead of
the generic Plug icon for MCP tool calls.
…ol output

Replace hardcoded gray colors with theme tokens throughout:
- CodeBlock: bg-gray-900/700 -> bg-surface-secondary/tertiary + border-border-light
- Mermaid dialog: bg-gray-700 -> bg-surface-secondary, text-gray-200 -> text-text-secondary
- Mermaid containers: rounded-xl -> rounded-lg, remove shadow-md for consistency
- ResultSwitcher: bg-gray-700 -> bg-surface-secondary with border separator
- RunCode: hover:bg-gray-700 -> hover:bg-surface-hover
- ErrorOutput: add border for visual consistency
- MermaidHeader/CodeWindowHeader: consistent focus outlines using border-heavy
Remove over-engineered tool output system (TableOutput, ErrorOutput,
detectOutputType) in favor of simple text extraction. Tool output now
extracts the text content from MCP content blocks and displays it as
clean readable text — no tables, no error styling, no JSON formatting.

Parameters only show key-value badges for simple objects; complex JSON
is hidden instead of dumped raw. Matches Cursor-style simplicity.
- Strip verbose MCP error prefixes (Error: [MCP][server][tool] tool call
  failed: Error POSTing...) and show just the meaningful error message
- Display errors in red text
- Format uniform JSON arrays as readable lists (name — path) instead
  of raw JSON dumps
- Format plain JSON objects as key: value lines
- Replace flat formatObject with recursive formatValue for proper
  indented display of nested JSON structures
- Add ComplexInput component for tool parameters with nested objects,
  arrays, or long strings (previously hidden)
- Broaden hasParams check to show parameters for all object types
- Add font-mono to output renderer for better alignment
Inline the 51 Simple Icons SVG paths used by LangIcon directly into
langIconPaths.ts, eliminating the runtime dependency on
@icons-pack/react-simple-icons (which requires Node >= 24).

- LangIcon now renders a plain <svg> with the path data instead of
  lazy-loading React components from the package
- Removes Suspense/React.lazy overhead for code block language icons
- SVG paths sourced from Simple Icons (CC0 1.0 license)
- Package kept in package.json for now (will be removed separately)
@github-actions

Copy link
Copy Markdown
Contributor

🚨 Unused i18next Keys Detected

The following translation keys are defined in translation.json but are not used in the codebase:

  • com_assistants_action_attempt
  • com_assistants_attempt_info
  • com_assistants_domain_info
  • com_ui_ui_resources

⚠️ Please remove these unused keys to keep the translation files clean.

@github-actions

Copy link
Copy Markdown
Contributor

🚨 Unused NPM Packages Detected

The following unused dependencies were found:

📂 Client client/package.json

  • @icons-pack/react-simple-icons

⚠️ Please remove these unused dependencies to keep your project clean.

…keys

- MCP tools without a custom iconPath now show Wrench instead of Plug,
  matching the generic tool fallback and avoiding the "plugin" metaphor
- Remove unused translation keys: com_assistants_action_attempt,
  com_assistants_attempt_info, com_assistants_domain_info,
  com_ui_ui_resources
@github-actions

Copy link
Copy Markdown
Contributor

🚨 Unused NPM Packages Detected

The following unused dependencies were found:

📂 Client client/package.json

  • @icons-pack/react-simple-icons

⚠️ Please remove these unused dependencies to keep your project clean.

- Combine 3x getToolMeta loop into single toolMetadata pass (ToolCallGroup)
- Extract sortPagesByRelevance to shared util (was duplicated in
  FilePreviewDialog and RetrievalCall)
- Deduplicate AGENT_STYLE_TOOLS Set (export from OpenAIImageGen/index.ts)
- Localize "source/sources" in WebSearch aria-label
- Add autoExpand useEffect to CodeAnalyze for live setting changes
- Log download errors in FilePreviewDialog instead of silently swallowing
- Replace @ts-ignore with @ts-expect-error + explanation in Code.tsx
- Remove dead currentContent alias in CodeMarkdown
@github-actions

Copy link
Copy Markdown
Contributor

🚨 Unused NPM Packages Detected

The following unused dependencies were found:

📂 Client client/package.json

  • @icons-pack/react-simple-icons

⚠️ Please remove these unused dependencies to keep your project clean.

…json and package-lock.json

- Deleted the @icons-pack/react-simple-icons entry from both package.json and package-lock.json, following the previous refactor to use inline SVGs for icons.
- Remove unused gIdx variable (ESLint error)
- Fix singular/plural in web search sources aria-label
- Separate inline type import in ToolCallGroup per AGENTS.md
- Remove non-existent placeholderDimensions prop from Image in Files.tsx
- Fix localize count param type (number, not string) in WebSearch.tsx
- Pass full resource object instead of partial in UIResourceCarousel.tsx
- Add 'as const' to toggleSwitchConfigs localizationKey in General.tsx
- Fix SearchResultData type in Citation.test.tsx
- Fix TAttachment and UIResource test fixture types across test files
The local formatBytes returns a human-readable string with units
("1.5 MB"), while ~/utils/formatBytes returns a raw number. They
serve different purposes, so the local copy is retained with a
JSDoc comment explaining the distinction.
@danny-avila danny-avila changed the title feat: redesign tool call UI with type-specific icons, smart grouping, and rich output rendering 🧩 feat: Redesign Tool Call UI with Contextual Icons, Smart Grouping, and Rich Output Rendering Mar 25, 2026
- Replace cancelled IIFE with documented ternary in OpenAIImageGen,
  explaining the agent vs legacy path distinction
- Add .catch() fallback to loadLowlight() in useLazyHighlight — falls
  back to plain text if the chunk fails to load
- Fix import ordering in ToolCallGroup.tsx (type imports grouped before
  local value imports per AGENTS.md)
- FilePreviewDialog: add cancelledRef guard to loadPreview so blob URLs
  are never created after the dialog closes (prevents orphaned object
  URLs on unmount during async PDF fetch)
- RetrievalCall: filter useGetFiles by fileIds from fileSources instead
  of fetching the entire user file corpus for display-only name matching
@danny-avila

Copy link
Copy Markdown
Owner

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e9282435cd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return <pre className="whitespace-pre-wrap text-xs text-text-primary">{input}</pre>;
}

return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Render non-object JSON params instead of returning null

When input is valid JSON but not an object (for example a JSON string, number, or array), hasParams can still evaluate to true, so the Parameters expander is shown, but InputRenderer falls through and returns null. That leaves users with an empty parameters panel and hides tool arguments that were previously visible as text. Add a fallback render path for successfully parsed non-object JSON values so the panel is never blank.

Useful? React with 👍 / 👎.

Replace the custom YAML-ish formatValue/formatObjectArray rendering
with JSON.stringify + hljs language-json styling. Structured API
responses (like GitHub search results) now display as proper
syntax-highlighted JSON with indentation instead of a flat key-value
text dump.

- Remove formatValue, formatObjectArray, isUniformObjectArray helpers
- Add isJson flag to extractText return type
- JSON output rendered in <code class="hljs language-json"> block
- Text content blocks (type: "text") still extracted and rendered
  as plain text
- Error output unchanged
Replace nested ternary with a named computeCancelled() function that
documents the agent vs legacy path branching. Resolves eslint
no-nested-ternary warning.
@danny-avila
danny-avila merged commit 0c66823 into dev Mar 25, 2026
13 checks passed
@danny-avila
danny-avila deleted the feat/plugins-ui branch March 25, 2026 16:31
jcbartle pushed a commit to jcbartle/LibreChat that referenced this pull request May 11, 2026
…and Rich Output Rendering (danny-avila#12163)

* feat: redesign tool call UI with type-specific icons, smart grouping, and rich output rendering

Replace the generic spinner/checkmark tool call UI with a modern, Cursor-inspired design:

- Add per-tool-type icons (Plug for MCP, Terminal for code, Globe for web search, etc.)
- Group 2+ consecutive tool calls into collapsible "Used N tools" sections
- Stack unique tool icons in grouped headers with overlapping circle design
- Replace raw JSON output with intelligent renderers (table, error, text)
- Restructure ToolCallInfo: output first, parameters collapsible at bottom
- Add shared useExpandCollapse hook for consistent animations
- Add CodeWindowHeader for ExecuteCode windowed view
- Remove FinishedIcon (purple checkmark) entirely

* feat: display custom MCP server icons in tool calls

Add useMCPIconMap hook to resolve MCP server names to their configured
icon paths. ToolIcon and StackedToolIcons now accept custom icon URLs,
showing actual server logos (e.g., Home Assistant, GitHub) instead of
the generic Plug icon for MCP tool calls.

* refactor: unify container styling across code blocks, mermaid, and tool output

Replace hardcoded gray colors with theme tokens throughout:
- CodeBlock: bg-gray-900/700 -> bg-surface-secondary/tertiary + border-border-light
- Mermaid dialog: bg-gray-700 -> bg-surface-secondary, text-gray-200 -> text-text-secondary
- Mermaid containers: rounded-xl -> rounded-lg, remove shadow-md for consistency
- ResultSwitcher: bg-gray-700 -> bg-surface-secondary with border separator
- RunCode: hover:bg-gray-700 -> hover:bg-surface-hover
- ErrorOutput: add border for visual consistency
- MermaidHeader/CodeWindowHeader: consistent focus outlines using border-heavy

* refactor: simplify tool output to plain text, remove custom renderers

Remove over-engineered tool output system (TableOutput, ErrorOutput,
detectOutputType) in favor of simple text extraction. Tool output now
extracts the text content from MCP content blocks and displays it as
clean readable text — no tables, no error styling, no JSON formatting.

Parameters only show key-value badges for simple objects; complex JSON
is hidden instead of dumped raw. Matches Cursor-style simplicity.

* fix: handle error messages and format JSON arrays in tool output

- Strip verbose MCP error prefixes (Error: [MCP][server][tool] tool call
  failed: Error POSTing...) and show just the meaningful error message
- Display errors in red text
- Format uniform JSON arrays as readable lists (name — path) instead
  of raw JSON dumps
- Format plain JSON objects as key: value lines

* feat: improve JSON display in tool call output and parameters

- Replace flat formatObject with recursive formatValue for proper
  indented display of nested JSON structures
- Add ComplexInput component for tool parameters with nested objects,
  arrays, or long strings (previously hidden)
- Broaden hasParams check to show parameters for all object types
- Add font-mono to output renderer for better alignment

* feat: add localization keys for tool errors, web search, and code UI

* refactor: move Mermaid components into dedicated directory module

* refactor: extract CodeBar, FloatingCodeBar, and copy utilities from CodeBlock

* feat: replace manual SVG icons with @icons-pack/react-simple-icons

Supports 50+ programming languages with tree-shaken brand icons
instead of hand-crafted SVGs for 19 languages.

* refactor: simplify code execution UI with persistent code toggle

* refactor: use useExpandCollapse hook in Thinking and Reasoning

* feat: improve tool call error states, subtitles, and group summaries

* feat: redesign web search with inline source display

* feat: improve agent handoff with keyboard accessibility

* feat: reorganize exports order in hooks and utils

* refactor: unify CopyCodeButton with animated icon transitions and iconOnly support

* feat: add run code state machine with animated success/error feedback

* refactor: improve ResultSwitcher with lucide icons and accessibility

* refactor: update CopyButton component

* refactor: replace CopyCodeButton with CopyButton component across multiple files

* test: add ImageGen test stubs

* test: add RetrievalCall test stubs

* feat: merge ImageGen with ToolIcon and localized progress text

* feat: modernize RetrievalCall with ToolIcon and collapsible output

* test: add getToolIconType action delimiter tests

* test: add ImageGen collapsible output tests

* feat: add action ToolIcon type with Zap icon

* fix: replace AgentHandoff div with semantic button

* feat: add aria-live regions to tool components

* feat: redesign execute_code tool UI with syntax highlighting and language icons

- Remove filename labels (script.py, main.rs) and line counter from CodeWindowHeader
- Replace generic FileCode icon with language-specific LangIcon
- Add syntax highlighting via highlight.js to code blocks
- Add SquareTerminal icon to ExecuteCode progress text
- Use shared CopyButton component in CodeWindowHeader
- Remove active:scale-95 press animation from CopyButton and RunCode

* feat: dynamic tool status text sizing based on markdown font-size variable

- Add tool-status-text CSS class using calc(0.9 * --markdown-font-size)
- Update progress-text-wrapper to use dynamic sizing instead of base size
- Apply tool-status-text to WebSearch, ToolCallGroup, AgentHandoff, ImageGen
- Replace hardcoded text-sm/text-xs with dynamic class across all tools
- Animate chevron rotation in ProgressText and ToolCallGroup
- Update subtitle text color from tertiary to secondary

* fix: consistent spacing and text styles across all tool components

- Standardize tool status row spacing to my-1/my-1.5 across all components
- Update ToolCallInfo text from tertiary to secondary, add vertical padding
- Animate ToolCallInfo parameters chevron rotation
- Update OutputRenderer link colors from tertiary to secondary

* feat: unify tool call grouping for all tool types

All consecutive tool calls (MCP, execute_code, web_search, image_gen,
file_search, code_interpreter) are now grouped under a single
collapsible "Used N tools" header instead of only grouping generic
tool calls.

- Remove SPECIAL_TOOL_NAMES blacklist from groupToolCalls
- Replace getToolCallData with getToolMeta to handle all tool types
- Use renderPart callback in ToolCallGroup for proper component routing
- Add file_search and code_interpreter mappings to getToolIconType

* feat: friendly tool group labels, more icons, and output copy button

- Show friendly names in group summary (Code, Web Search, Image
  Generation) instead of raw tool names
- Display MCP server names instead of individual function names
- Deduplicate labels and show up to 3 with +N overflow
- Increase stacked icons from 3 to 4
- Add icon-only copy button to tool output (OutputRenderer)

* fix: execute_code spacing and syntax-highlighted code visibility

Match ToolCall spacing by using my-1.5 on status line and moving my-2
inside overflow-hidden. Replace broken hljs.highlight() with lowlight
(same engine used by rehype-highlight for markdown code blocks) to
render syntax-highlighted code as React elements. Handle object args
in useParseArgs to support both string and Record arg formats.

* feat: replace showCode with auto-expand tools setting

Replace the execute_code-only "Always show code when using code
interpreter" global toggle with a new "Auto-expand tool details"
setting that controls all tool types. Each tool instance now uses
independent local state initialized from the setting, so expanding
one tool no longer affects others. Applies to ToolCall, ExecuteCode,
ToolCallGroup, and CodeAnalyze components.

* fix: apply auto-expand tools setting to WebSearch and RetrievalCall

* fix: only auto-expand tools when content is available

Defer auto-expansion until tool output or content arrives, preventing
empty bordered containers from showing while tools are still running.
Uses useEffect to expand when output becomes available during streaming.

* feat: redesign file_search tool output, citations, and file preview

- Redesign RetrievalCall with per-file cards using OutputRenderer
  (truncated content with show more/less, copy button) matching MCP
  tool pattern
- Route file_search tool calls from Agents API to RetrievalCall
  instead of generic ToolCall
- Add FilePreviewDialog for viewing files (PDF iframe, text content)
  with download option, opened from clickable filenames
- Redesign file citations: FileText icon in badge, relevance and
  page numbers in hovercard, click opens file preview instead of
  downloading
- Add file preview to message file attachments (Files.tsx)
- Fix hovercard animation to slide top-to-bottom and dismiss
  instantly on file click to prevent glitching over dialog
- Add localization keys for relevance, extracted content, preview
- Add top margin to ToolCallGroup

* chore: remove leftover .planning files

* fix: polish FilePreviewDialog, CodeBlock, LangIcon, and Sources

* fix: prevent keyboard focus on collapsed tool content

Add inert attribute to all expand/collapse wrapper divs so
collapsed content is removed from tab order and hidden from
assistive technology. Skip disabled ProgressText buttons from
tab order with tabIndex={-1}.

* feat: integrate file metadata into file_search UI

Pass fileType (MIME) and fileBytes from backend file records through
to the frontend. Add file-type-specific icons, file size display,
pages sorted by relevance, multi-snippet content per file, smart
preview detection by MIME type, and copy button in file preview dialog.

* fix: review fixes — inverted type check, wrong dimension, missing import, fail-open perms, timer leaks, dead code cleanup

* fix: update CodeBlock styling for improved visual consistency

* fix(chat): open composite file citations in preview

* fix(chat): restore file previews for parsed search results

* chore(git): ignore bg-shell artifacts

* fix(chat): restore readable code content in light theme

* style(chat): align code and output surfaces by theme

* chore(i18n): remove 6 unused translation keys

* fix(deps): replace private registry URL with public npm registry in lockfile

* fix: CI lint, build, and test failures

- Add missing scaleImage utility (fixes Vite build error)
- Export scaleImage from utils/index.ts
- Remove unused imports from Part.tsx (FunctionToolCall, CodeToolCall, Agents)
- Fix prettier formatting in Part.tsx (multi-line → single-line imports, conditions)
- Remove excess blank lines in Part.tsx
- Remove unused CodeEditorRef import from Artifacts.tsx
- Add useProgress mock to OpenAIImageGen.test.tsx
- Add scaleImage mock to OpenAIImageGen.test.tsx
- Update OpenAIImageGen tests to match redesigned component structure
- Remove dead collapsible output panel tests from ImageGen.test.tsx
- Add @icons-pack/react-simple-icons to Jest transformIgnorePatterns (ESM fix)

* refactor: reorganize imports order across multiple components for consistency

* fix: add scaleImage tests, delete dead ImageGen wrapper, wire up onUIAction in ToolCallInfo

- Add 7 unit tests for scaleImage utility covering null ref, scaling,
  no-upscale, height clamping, landscape, and panoramic images
- Delete unused Content/ImageGen.tsx re-export wrapper (ImageGen is
  imported from Parts/OpenAIImageGen via the Parts barrel)
- Wire up onUIAction in ToolCallInfo to use handleUIAction + ask from
  useMessagesOperations, matching UIResourceCarousel's behavior
  (was previously a silent no-op)

* refactor: optimize imports and enhance lazy loading for language icons

* fix: address review findings for tool call UI redesign

- Fix unstable array-index keys in ToolCallGroup (streaming state corruption)
- Add plain-text fallback in InputRenderer for non-JSON tool args
- Localize FRIENDLY_NAMES via translation keys instead of hardcoded English
- Guard autoCollapse against user-initiated manual expansion
- Fix CODE_INTERPRETER hasOutput to check actual outputs instead of hardcoding true
- Add logger.warn for Citations fail-closed behavior on permission errors
- Add Terminal icon to CodeAnalyze ProgressText for visual consistency
- Fix getMCPServerName to use indexOf instead of fragile split
- Use useLayoutEffect for inert attribute in useExpandCollapse (a11y)
- Memoize style object in useExpandCollapse to avoid defeating React.memo
- Memoize groupSequentialToolCalls in ContentParts to avoid recomputation
- Use source.link as stable key instead of array index in WebSearch
- Hoist rehypePlugins outside CodeMarkdown to prevent per-render recreation

* fix: revert useMemo after conditional returns in ContentParts

The useMemo placed after early returns violated React Rules of Hooks —
hook call count would change when transitioning between edit/view mode.
Reverted to the original plain forEach which is correct and equally
performant since content changes on every streaming token anyway.

* chore: remove unused com_ui_variables_info translation key

* fix: update tests and jest config for ESM compatibility after rebase

- Add ESM-only packages to transformIgnorePatterns (@dicebear, unified
  ecosystem, react-dnd, lowlight, etc.) to fix Jest parse failures
  introduced by dev rebase
- Update ToolCall.test.tsx to match new component API (CSS
  expand/collapse instead of conditional rendering, simplified props)
- Update ToolCallInfo.test.tsx to mock OutputRenderer (avoids ESM
  chain), align with current component interface (input/output/attachments)

* refactor: replace @icons-pack/react-simple-icons with inline SVGs

Inline the 51 Simple Icons SVG paths used by LangIcon directly into
langIconPaths.ts, eliminating the runtime dependency on
@icons-pack/react-simple-icons (which requires Node >= 24).

- LangIcon now renders a plain <svg> with the path data instead of
  lazy-loading React components from the package
- Removes Suspense/React.lazy overhead for code block language icons
- SVG paths sourced from Simple Icons (CC0 1.0 license)
- Package kept in package.json for now (will be removed separately)

* fix: replace Plug icon with Wrench for MCP tools, remove unused i18n keys

- MCP tools without a custom iconPath now show Wrench instead of Plug,
  matching the generic tool fallback and avoiding the "plugin" metaphor
- Remove unused translation keys: com_assistants_action_attempt,
  com_assistants_attempt_info, com_assistants_domain_info,
  com_ui_ui_resources

* fix: address second review findings

- Combine 3x getToolMeta loop into single toolMetadata pass (ToolCallGroup)
- Extract sortPagesByRelevance to shared util (was duplicated in
  FilePreviewDialog and RetrievalCall)
- Deduplicate AGENT_STYLE_TOOLS Set (export from OpenAIImageGen/index.ts)
- Localize "source/sources" in WebSearch aria-label
- Add autoExpand useEffect to CodeAnalyze for live setting changes
- Log download errors in FilePreviewDialog instead of silently swallowing
- Replace @ts-ignore with @ts-expect-error + explanation in Code.tsx
- Remove dead currentContent alias in CodeMarkdown

* chore: remove @icons-pack/react-simple-icons dependency from package.json and package-lock.json

- Deleted the @icons-pack/react-simple-icons entry from both package.json and package-lock.json, following the previous refactor to use inline SVGs for icons.

* fix: address triage audit findings

- Remove unused gIdx variable (ESLint error)
- Fix singular/plural in web search sources aria-label
- Separate inline type import in ToolCallGroup per AGENTS.md

* fix: remove invalid placeholderDimensions prop from Image component

* chore: import order

* chore: import order

* fix: resolve TypeScript errors in PR-touched files

- Remove non-existent placeholderDimensions prop from Image in Files.tsx
- Fix localize count param type (number, not string) in WebSearch.tsx
- Pass full resource object instead of partial in UIResourceCarousel.tsx
- Add 'as const' to toggleSwitchConfigs localizationKey in General.tsx
- Fix SearchResultData type in Citation.test.tsx
- Fix TAttachment and UIResource test fixture types across test files

* docs: document formatBytes difference in FilePreviewDialog

The local formatBytes returns a human-readable string with units
("1.5 MB"), while ~/utils/formatBytes returns a raw number. They
serve different purposes, so the local copy is retained with a
JSDoc comment explaining the distinction.

* fix: address remaining review items

- Replace cancelled IIFE with documented ternary in OpenAIImageGen,
  explaining the agent vs legacy path distinction
- Add .catch() fallback to loadLowlight() in useLazyHighlight — falls
  back to plain text if the chunk fails to load
- Fix import ordering in ToolCallGroup.tsx (type imports grouped before
  local value imports per AGENTS.md)

* fix: blob URL leak and useGetFiles over-fetch

- FilePreviewDialog: add cancelledRef guard to loadPreview so blob URLs
  are never created after the dialog closes (prevents orphaned object
  URLs on unmount during async PDF fetch)
- RetrievalCall: filter useGetFiles by fileIds from fileSources instead
  of fetching the entire user file corpus for display-only name matching

* chore: fix com_nav_auto_expand_tools alphabetical order in translation.json

* fix: render non-object JSON params instead of returning null in InputRenderer

* refactor: render JSON tool output as syntax-highlighted code block

Replace the custom YAML-ish formatValue/formatObjectArray rendering
with JSON.stringify + hljs language-json styling. Structured API
responses (like GitHub search results) now display as proper
syntax-highlighted JSON with indentation instead of a flat key-value
text dump.

- Remove formatValue, formatObjectArray, isUniformObjectArray helpers
- Add isJson flag to extractText return type
- JSON output rendered in <code class="hljs language-json"> block
- Text content blocks (type: "text") still extracted and rendered
  as plain text
- Error output unchanged

* fix: extract cancelled IIFE to named function in OpenAIImageGen

Replace nested ternary with a named computeCancelled() function that
documents the agent vs legacy path branching. Resolves eslint
no-nested-ternary warning.

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
ThomasVuNguyen pushed a commit to ThomasVuNguyen/LibreChat that referenced this pull request Jul 15, 2026
…and Rich Output Rendering (danny-avila#12163)

* feat: redesign tool call UI with type-specific icons, smart grouping, and rich output rendering

Replace the generic spinner/checkmark tool call UI with a modern, Cursor-inspired design:

- Add per-tool-type icons (Plug for MCP, Terminal for code, Globe for web search, etc.)
- Group 2+ consecutive tool calls into collapsible "Used N tools" sections
- Stack unique tool icons in grouped headers with overlapping circle design
- Replace raw JSON output with intelligent renderers (table, error, text)
- Restructure ToolCallInfo: output first, parameters collapsible at bottom
- Add shared useExpandCollapse hook for consistent animations
- Add CodeWindowHeader for ExecuteCode windowed view
- Remove FinishedIcon (purple checkmark) entirely

* feat: display custom MCP server icons in tool calls

Add useMCPIconMap hook to resolve MCP server names to their configured
icon paths. ToolIcon and StackedToolIcons now accept custom icon URLs,
showing actual server logos (e.g., Home Assistant, GitHub) instead of
the generic Plug icon for MCP tool calls.

* refactor: unify container styling across code blocks, mermaid, and tool output

Replace hardcoded gray colors with theme tokens throughout:
- CodeBlock: bg-gray-900/700 -> bg-surface-secondary/tertiary + border-border-light
- Mermaid dialog: bg-gray-700 -> bg-surface-secondary, text-gray-200 -> text-text-secondary
- Mermaid containers: rounded-xl -> rounded-lg, remove shadow-md for consistency
- ResultSwitcher: bg-gray-700 -> bg-surface-secondary with border separator
- RunCode: hover:bg-gray-700 -> hover:bg-surface-hover
- ErrorOutput: add border for visual consistency
- MermaidHeader/CodeWindowHeader: consistent focus outlines using border-heavy

* refactor: simplify tool output to plain text, remove custom renderers

Remove over-engineered tool output system (TableOutput, ErrorOutput,
detectOutputType) in favor of simple text extraction. Tool output now
extracts the text content from MCP content blocks and displays it as
clean readable text — no tables, no error styling, no JSON formatting.

Parameters only show key-value badges for simple objects; complex JSON
is hidden instead of dumped raw. Matches Cursor-style simplicity.

* fix: handle error messages and format JSON arrays in tool output

- Strip verbose MCP error prefixes (Error: [MCP][server][tool] tool call
  failed: Error POSTing...) and show just the meaningful error message
- Display errors in red text
- Format uniform JSON arrays as readable lists (name — path) instead
  of raw JSON dumps
- Format plain JSON objects as key: value lines

* feat: improve JSON display in tool call output and parameters

- Replace flat formatObject with recursive formatValue for proper
  indented display of nested JSON structures
- Add ComplexInput component for tool parameters with nested objects,
  arrays, or long strings (previously hidden)
- Broaden hasParams check to show parameters for all object types
- Add font-mono to output renderer for better alignment

* feat: add localization keys for tool errors, web search, and code UI

* refactor: move Mermaid components into dedicated directory module

* refactor: extract CodeBar, FloatingCodeBar, and copy utilities from CodeBlock

* feat: replace manual SVG icons with @icons-pack/react-simple-icons

Supports 50+ programming languages with tree-shaken brand icons
instead of hand-crafted SVGs for 19 languages.

* refactor: simplify code execution UI with persistent code toggle

* refactor: use useExpandCollapse hook in Thinking and Reasoning

* feat: improve tool call error states, subtitles, and group summaries

* feat: redesign web search with inline source display

* feat: improve agent handoff with keyboard accessibility

* feat: reorganize exports order in hooks and utils

* refactor: unify CopyCodeButton with animated icon transitions and iconOnly support

* feat: add run code state machine with animated success/error feedback

* refactor: improve ResultSwitcher with lucide icons and accessibility

* refactor: update CopyButton component

* refactor: replace CopyCodeButton with CopyButton component across multiple files

* test: add ImageGen test stubs

* test: add RetrievalCall test stubs

* feat: merge ImageGen with ToolIcon and localized progress text

* feat: modernize RetrievalCall with ToolIcon and collapsible output

* test: add getToolIconType action delimiter tests

* test: add ImageGen collapsible output tests

* feat: add action ToolIcon type with Zap icon

* fix: replace AgentHandoff div with semantic button

* feat: add aria-live regions to tool components

* feat: redesign execute_code tool UI with syntax highlighting and language icons

- Remove filename labels (script.py, main.rs) and line counter from CodeWindowHeader
- Replace generic FileCode icon with language-specific LangIcon
- Add syntax highlighting via highlight.js to code blocks
- Add SquareTerminal icon to ExecuteCode progress text
- Use shared CopyButton component in CodeWindowHeader
- Remove active:scale-95 press animation from CopyButton and RunCode

* feat: dynamic tool status text sizing based on markdown font-size variable

- Add tool-status-text CSS class using calc(0.9 * --markdown-font-size)
- Update progress-text-wrapper to use dynamic sizing instead of base size
- Apply tool-status-text to WebSearch, ToolCallGroup, AgentHandoff, ImageGen
- Replace hardcoded text-sm/text-xs with dynamic class across all tools
- Animate chevron rotation in ProgressText and ToolCallGroup
- Update subtitle text color from tertiary to secondary

* fix: consistent spacing and text styles across all tool components

- Standardize tool status row spacing to my-1/my-1.5 across all components
- Update ToolCallInfo text from tertiary to secondary, add vertical padding
- Animate ToolCallInfo parameters chevron rotation
- Update OutputRenderer link colors from tertiary to secondary

* feat: unify tool call grouping for all tool types

All consecutive tool calls (MCP, execute_code, web_search, image_gen,
file_search, code_interpreter) are now grouped under a single
collapsible "Used N tools" header instead of only grouping generic
tool calls.

- Remove SPECIAL_TOOL_NAMES blacklist from groupToolCalls
- Replace getToolCallData with getToolMeta to handle all tool types
- Use renderPart callback in ToolCallGroup for proper component routing
- Add file_search and code_interpreter mappings to getToolIconType

* feat: friendly tool group labels, more icons, and output copy button

- Show friendly names in group summary (Code, Web Search, Image
  Generation) instead of raw tool names
- Display MCP server names instead of individual function names
- Deduplicate labels and show up to 3 with +N overflow
- Increase stacked icons from 3 to 4
- Add icon-only copy button to tool output (OutputRenderer)

* fix: execute_code spacing and syntax-highlighted code visibility

Match ToolCall spacing by using my-1.5 on status line and moving my-2
inside overflow-hidden. Replace broken hljs.highlight() with lowlight
(same engine used by rehype-highlight for markdown code blocks) to
render syntax-highlighted code as React elements. Handle object args
in useParseArgs to support both string and Record arg formats.

* feat: replace showCode with auto-expand tools setting

Replace the execute_code-only "Always show code when using code
interpreter" global toggle with a new "Auto-expand tool details"
setting that controls all tool types. Each tool instance now uses
independent local state initialized from the setting, so expanding
one tool no longer affects others. Applies to ToolCall, ExecuteCode,
ToolCallGroup, and CodeAnalyze components.

* fix: apply auto-expand tools setting to WebSearch and RetrievalCall

* fix: only auto-expand tools when content is available

Defer auto-expansion until tool output or content arrives, preventing
empty bordered containers from showing while tools are still running.
Uses useEffect to expand when output becomes available during streaming.

* feat: redesign file_search tool output, citations, and file preview

- Redesign RetrievalCall with per-file cards using OutputRenderer
  (truncated content with show more/less, copy button) matching MCP
  tool pattern
- Route file_search tool calls from Agents API to RetrievalCall
  instead of generic ToolCall
- Add FilePreviewDialog for viewing files (PDF iframe, text content)
  with download option, opened from clickable filenames
- Redesign file citations: FileText icon in badge, relevance and
  page numbers in hovercard, click opens file preview instead of
  downloading
- Add file preview to message file attachments (Files.tsx)
- Fix hovercard animation to slide top-to-bottom and dismiss
  instantly on file click to prevent glitching over dialog
- Add localization keys for relevance, extracted content, preview
- Add top margin to ToolCallGroup

* chore: remove leftover .planning files

* fix: polish FilePreviewDialog, CodeBlock, LangIcon, and Sources

* fix: prevent keyboard focus on collapsed tool content

Add inert attribute to all expand/collapse wrapper divs so
collapsed content is removed from tab order and hidden from
assistive technology. Skip disabled ProgressText buttons from
tab order with tabIndex={-1}.

* feat: integrate file metadata into file_search UI

Pass fileType (MIME) and fileBytes from backend file records through
to the frontend. Add file-type-specific icons, file size display,
pages sorted by relevance, multi-snippet content per file, smart
preview detection by MIME type, and copy button in file preview dialog.

* fix: review fixes — inverted type check, wrong dimension, missing import, fail-open perms, timer leaks, dead code cleanup

* fix: update CodeBlock styling for improved visual consistency

* fix(chat): open composite file citations in preview

* fix(chat): restore file previews for parsed search results

* chore(git): ignore bg-shell artifacts

* fix(chat): restore readable code content in light theme

* style(chat): align code and output surfaces by theme

* chore(i18n): remove 6 unused translation keys

* fix(deps): replace private registry URL with public npm registry in lockfile

* fix: CI lint, build, and test failures

- Add missing scaleImage utility (fixes Vite build error)
- Export scaleImage from utils/index.ts
- Remove unused imports from Part.tsx (FunctionToolCall, CodeToolCall, Agents)
- Fix prettier formatting in Part.tsx (multi-line → single-line imports, conditions)
- Remove excess blank lines in Part.tsx
- Remove unused CodeEditorRef import from Artifacts.tsx
- Add useProgress mock to OpenAIImageGen.test.tsx
- Add scaleImage mock to OpenAIImageGen.test.tsx
- Update OpenAIImageGen tests to match redesigned component structure
- Remove dead collapsible output panel tests from ImageGen.test.tsx
- Add @icons-pack/react-simple-icons to Jest transformIgnorePatterns (ESM fix)

* refactor: reorganize imports order across multiple components for consistency

* fix: add scaleImage tests, delete dead ImageGen wrapper, wire up onUIAction in ToolCallInfo

- Add 7 unit tests for scaleImage utility covering null ref, scaling,
  no-upscale, height clamping, landscape, and panoramic images
- Delete unused Content/ImageGen.tsx re-export wrapper (ImageGen is
  imported from Parts/OpenAIImageGen via the Parts barrel)
- Wire up onUIAction in ToolCallInfo to use handleUIAction + ask from
  useMessagesOperations, matching UIResourceCarousel's behavior
  (was previously a silent no-op)

* refactor: optimize imports and enhance lazy loading for language icons

* fix: address review findings for tool call UI redesign

- Fix unstable array-index keys in ToolCallGroup (streaming state corruption)
- Add plain-text fallback in InputRenderer for non-JSON tool args
- Localize FRIENDLY_NAMES via translation keys instead of hardcoded English
- Guard autoCollapse against user-initiated manual expansion
- Fix CODE_INTERPRETER hasOutput to check actual outputs instead of hardcoding true
- Add logger.warn for Citations fail-closed behavior on permission errors
- Add Terminal icon to CodeAnalyze ProgressText for visual consistency
- Fix getMCPServerName to use indexOf instead of fragile split
- Use useLayoutEffect for inert attribute in useExpandCollapse (a11y)
- Memoize style object in useExpandCollapse to avoid defeating React.memo
- Memoize groupSequentialToolCalls in ContentParts to avoid recomputation
- Use source.link as stable key instead of array index in WebSearch
- Hoist rehypePlugins outside CodeMarkdown to prevent per-render recreation

* fix: revert useMemo after conditional returns in ContentParts

The useMemo placed after early returns violated React Rules of Hooks —
hook call count would change when transitioning between edit/view mode.
Reverted to the original plain forEach which is correct and equally
performant since content changes on every streaming token anyway.

* chore: remove unused com_ui_variables_info translation key

* fix: update tests and jest config for ESM compatibility after rebase

- Add ESM-only packages to transformIgnorePatterns (@dicebear, unified
  ecosystem, react-dnd, lowlight, etc.) to fix Jest parse failures
  introduced by dev rebase
- Update ToolCall.test.tsx to match new component API (CSS
  expand/collapse instead of conditional rendering, simplified props)
- Update ToolCallInfo.test.tsx to mock OutputRenderer (avoids ESM
  chain), align with current component interface (input/output/attachments)

* refactor: replace @icons-pack/react-simple-icons with inline SVGs

Inline the 51 Simple Icons SVG paths used by LangIcon directly into
langIconPaths.ts, eliminating the runtime dependency on
@icons-pack/react-simple-icons (which requires Node >= 24).

- LangIcon now renders a plain <svg> with the path data instead of
  lazy-loading React components from the package
- Removes Suspense/React.lazy overhead for code block language icons
- SVG paths sourced from Simple Icons (CC0 1.0 license)
- Package kept in package.json for now (will be removed separately)

* fix: replace Plug icon with Wrench for MCP tools, remove unused i18n keys

- MCP tools without a custom iconPath now show Wrench instead of Plug,
  matching the generic tool fallback and avoiding the "plugin" metaphor
- Remove unused translation keys: com_assistants_action_attempt,
  com_assistants_attempt_info, com_assistants_domain_info,
  com_ui_ui_resources

* fix: address second review findings

- Combine 3x getToolMeta loop into single toolMetadata pass (ToolCallGroup)
- Extract sortPagesByRelevance to shared util (was duplicated in
  FilePreviewDialog and RetrievalCall)
- Deduplicate AGENT_STYLE_TOOLS Set (export from OpenAIImageGen/index.ts)
- Localize "source/sources" in WebSearch aria-label
- Add autoExpand useEffect to CodeAnalyze for live setting changes
- Log download errors in FilePreviewDialog instead of silently swallowing
- Replace @ts-ignore with @ts-expect-error + explanation in Code.tsx
- Remove dead currentContent alias in CodeMarkdown

* chore: remove @icons-pack/react-simple-icons dependency from package.json and package-lock.json

- Deleted the @icons-pack/react-simple-icons entry from both package.json and package-lock.json, following the previous refactor to use inline SVGs for icons.

* fix: address triage audit findings

- Remove unused gIdx variable (ESLint error)
- Fix singular/plural in web search sources aria-label
- Separate inline type import in ToolCallGroup per AGENTS.md

* fix: remove invalid placeholderDimensions prop from Image component

* chore: import order

* chore: import order

* fix: resolve TypeScript errors in PR-touched files

- Remove non-existent placeholderDimensions prop from Image in Files.tsx
- Fix localize count param type (number, not string) in WebSearch.tsx
- Pass full resource object instead of partial in UIResourceCarousel.tsx
- Add 'as const' to toggleSwitchConfigs localizationKey in General.tsx
- Fix SearchResultData type in Citation.test.tsx
- Fix TAttachment and UIResource test fixture types across test files

* docs: document formatBytes difference in FilePreviewDialog

The local formatBytes returns a human-readable string with units
("1.5 MB"), while ~/utils/formatBytes returns a raw number. They
serve different purposes, so the local copy is retained with a
JSDoc comment explaining the distinction.

* fix: address remaining review items

- Replace cancelled IIFE with documented ternary in OpenAIImageGen,
  explaining the agent vs legacy path distinction
- Add .catch() fallback to loadLowlight() in useLazyHighlight — falls
  back to plain text if the chunk fails to load
- Fix import ordering in ToolCallGroup.tsx (type imports grouped before
  local value imports per AGENTS.md)

* fix: blob URL leak and useGetFiles over-fetch

- FilePreviewDialog: add cancelledRef guard to loadPreview so blob URLs
  are never created after the dialog closes (prevents orphaned object
  URLs on unmount during async PDF fetch)
- RetrievalCall: filter useGetFiles by fileIds from fileSources instead
  of fetching the entire user file corpus for display-only name matching

* chore: fix com_nav_auto_expand_tools alphabetical order in translation.json

* fix: render non-object JSON params instead of returning null in InputRenderer

* refactor: render JSON tool output as syntax-highlighted code block

Replace the custom YAML-ish formatValue/formatObjectArray rendering
with JSON.stringify + hljs language-json styling. Structured API
responses (like GitHub search results) now display as proper
syntax-highlighted JSON with indentation instead of a flat key-value
text dump.

- Remove formatValue, formatObjectArray, isUniformObjectArray helpers
- Add isJson flag to extractText return type
- JSON output rendered in <code class="hljs language-json"> block
- Text content blocks (type: "text") still extracted and rendered
  as plain text
- Error output unchanged

* fix: extract cancelled IIFE to named function in OpenAIImageGen

Replace nested ternary with a named computeCancelled() function that
documents the agent vs legacy path branching. Resolves eslint
no-nested-ternary warning.

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🎨 design UI/UX improvements ✨ enhancement New feature or request 🔨 refactor Code cleanup without new features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants