mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-20 17:13:44 +00:00
✂️ fix: Truncate Overflowing Activity and Intent Labels in Chat UI (#14607)
* ✂️ fix: Truncate Overflowing Activity and Intent Labels in Chat UI * 🖍️ style: Use Middle Dot Separator in Tool Label Chrome * ✂️ fix: Truncate Completed Web Search Label
This commit is contained in:
parent
f738810c11
commit
5029dd467e
13 changed files with 34 additions and 27 deletions
|
|
@ -164,7 +164,7 @@ const Part = memo(function Part({
|
|||
const failed = part.status === 'failed' || part.status === 'partial';
|
||||
return (
|
||||
<div
|
||||
className={`my-1 pl-1 text-sm italic ${failed ? 'text-amber-600 dark:text-amber-400' : 'text-text-secondary'}`}
|
||||
className={`my-1 break-words pl-1 text-sm italic ${failed ? 'text-amber-600 dark:text-amber-400' : 'text-text-secondary'}`}
|
||||
>
|
||||
{display}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ export default function BashCall({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="relative my-1.5 flex size-5 shrink-0 items-center gap-2.5">
|
||||
<div className="relative my-1.5 flex h-5 shrink-0 items-center gap-2.5">
|
||||
<ProgressText
|
||||
progress={progress}
|
||||
onClick={toggleCode}
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ export default function ExecuteCode({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="relative my-1.5 flex size-5 shrink-0 items-center gap-2.5">
|
||||
<div className="relative my-1.5 flex h-5 shrink-0 items-center gap-2.5">
|
||||
<ProgressText
|
||||
progress={progress}
|
||||
onClick={toggleCode}
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ export default function FileAuthoringCall({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="relative my-1.5 flex size-5 shrink-0 items-center gap-2.5">
|
||||
<div className="relative my-1.5 flex h-5 shrink-0 items-center gap-2.5">
|
||||
<ProgressText
|
||||
progress={progress}
|
||||
onClick={toggleCode}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export default function ProgressText({
|
|||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'progress-text-content tool-status-text whitespace-nowrap font-medium',
|
||||
'progress-text-content tool-status-text min-w-0 truncate font-medium',
|
||||
progress < 1 && 'shimmer',
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ export default function PendingSkillCall({
|
|||
: localize('com_ui_skill_running', { 0: skillName });
|
||||
|
||||
return (
|
||||
<div className="relative my-1.5 flex size-5 shrink-0 items-center gap-2.5">
|
||||
<div className="relative my-1.5 flex h-5 shrink-0 items-center gap-2.5">
|
||||
<div className="progress-text-wrapper text-token-text-secondary relative -mt-[0.75px] h-5 w-full leading-5">
|
||||
<div
|
||||
className="progress-text-content absolute left-0 top-0 overflow-visible whitespace-nowrap"
|
||||
className="progress-text-content absolute left-0 top-0 max-w-full overflow-visible whitespace-nowrap"
|
||||
style={{ opacity: 1, transform: 'none' }}
|
||||
>
|
||||
<div className="inline-flex w-full items-center gap-2">
|
||||
|
|
@ -44,7 +44,7 @@ export default function PendingSkillCall({
|
|||
className={cn('size-4 shrink-0 text-text-secondary', !loaded && 'animate-pulse')}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className={cn(!loaded && 'shimmer', 'font-medium')}>{text}</span>
|
||||
<span className={cn(!loaded && 'shimmer', 'min-w-0 truncate font-medium')}>{text}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ export default function ReadFileCall({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="relative my-1.5 flex size-5 shrink-0 items-center gap-2.5">
|
||||
<div className="relative my-1.5 flex h-5 shrink-0 items-center gap-2.5">
|
||||
<ProgressText
|
||||
progress={progress}
|
||||
onClick={toggleCode}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export default function SkillCall({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="relative my-1.5 flex size-5 shrink-0 items-center gap-2.5">
|
||||
<div className="relative my-1.5 flex h-5 shrink-0 items-center gap-2.5">
|
||||
<ProgressText
|
||||
progress={progress}
|
||||
onClick={toggleCode}
|
||||
|
|
|
|||
|
|
@ -489,7 +489,9 @@ export default function SubagentCall({
|
|||
<Users size={14} />
|
||||
)}
|
||||
</div>
|
||||
<span className="shrink-0">{headerText}</span>
|
||||
<span className="min-w-0 truncate" title={headerText}>
|
||||
{headerText}
|
||||
</span>
|
||||
{subagentNameLabel ? (
|
||||
<span
|
||||
className="min-w-0 flex-1 truncate font-normal text-text-secondary"
|
||||
|
|
|
|||
|
|
@ -6,15 +6,18 @@ import { cn } from '~/utils';
|
|||
const wrapperClass =
|
||||
'progress-text-wrapper text-token-text-secondary relative -mt-[0.75px] h-5 w-full leading-5';
|
||||
|
||||
/** `max-w-full` caps the absolutely-positioned line at the message column;
|
||||
* the label span truncates itself, so overflow stays visible for the
|
||||
* button's focus ring. */
|
||||
const contentClass =
|
||||
'progress-text-content absolute left-0 top-0 max-w-full overflow-visible whitespace-nowrap';
|
||||
|
||||
const Wrapper = ({ popover, children }: { popover: boolean; children: React.ReactNode }) => {
|
||||
if (popover) {
|
||||
return (
|
||||
<div className={wrapperClass}>
|
||||
<Popover.Trigger asChild>
|
||||
<div
|
||||
className="progress-text-content absolute left-0 top-0 overflow-visible whitespace-nowrap"
|
||||
style={{ opacity: 1, transform: 'none' }}
|
||||
>
|
||||
<div className={contentClass} style={{ opacity: 1, transform: 'none' }}>
|
||||
{children}
|
||||
</div>
|
||||
</Popover.Trigger>
|
||||
|
|
@ -24,10 +27,7 @@ const Wrapper = ({ popover, children }: { popover: boolean; children: React.Reac
|
|||
|
||||
return (
|
||||
<div className={wrapperClass}>
|
||||
<div
|
||||
className="progress-text-content absolute left-0 top-0 overflow-visible whitespace-nowrap"
|
||||
style={{ opacity: 1, transform: 'none' }}
|
||||
>
|
||||
<div className={contentClass} style={{ opacity: 1, transform: 'none' }}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -98,10 +98,12 @@ export default function ProgressText({
|
|||
aria-expanded={hasInput ? isExpanded : undefined}
|
||||
>
|
||||
{icon}
|
||||
<span className={cn(showShimmer ? 'shimmer' : '', 'font-medium')}>{text}</span>
|
||||
<span className={cn(showShimmer ? 'shimmer' : '', 'min-w-0 truncate font-medium')}>
|
||||
{text}
|
||||
</span>
|
||||
{subtitle && <span className="font-normal text-text-secondary">{subtitle}</span>}
|
||||
{errorSuffix && (
|
||||
<span className="font-normal text-red-600 dark:text-red-400">— {errorSuffix}</span>
|
||||
<span className="font-normal text-red-600 dark:text-red-400">· {errorSuffix}</span>
|
||||
)}
|
||||
{hasInput && (
|
||||
<ChevronDown
|
||||
|
|
|
|||
|
|
@ -376,10 +376,11 @@ export default function ToolCallGroup({
|
|||
)}
|
||||
<span
|
||||
className={cn(
|
||||
'tool-status-text font-medium',
|
||||
'tool-status-text min-w-0 truncate font-medium',
|
||||
activityFailed && 'text-amber-600 dark:text-amber-400',
|
||||
)}
|
||||
role="status"
|
||||
title={groupLabel}
|
||||
>
|
||||
{groupLabel}
|
||||
</span>
|
||||
|
|
@ -387,7 +388,9 @@ export default function ToolCallGroup({
|
|||
* questions) — every entry deduplicates to the same token, which
|
||||
* adds noise without info. Mixed groups keep the summary. */}
|
||||
{toolNameSummary && !allSubagents && !allAskQuestions && (
|
||||
<span className="text-xs font-normal text-text-secondary">— {toolNameSummary}</span>
|
||||
<span className="min-w-0 max-w-[40%] truncate text-xs font-normal text-text-secondary">
|
||||
· {toolNameSummary}
|
||||
</span>
|
||||
)}
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ export default function WebSearch({
|
|||
) : (
|
||||
<Globe className="size-4 shrink-0 text-text-secondary" aria-hidden="true" />
|
||||
)}
|
||||
<span className="font-medium">{completedText}</span>
|
||||
<span className="min-w-0 truncate font-medium">{completedText}</span>
|
||||
{hasSourceData && (
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
|
|
@ -289,7 +289,7 @@ export default function WebSearch({
|
|||
</span>
|
||||
{showSources && <StackedFavicons sources={streamingSources} start={-5} />}
|
||||
<Globe className="size-4 shrink-0 text-text-secondary" aria-hidden="true" />
|
||||
<span className="tool-status-text shimmer font-medium text-text-secondary">
|
||||
<span className="tool-status-text shimmer min-w-0 truncate font-medium text-text-secondary">
|
||||
{progressText}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -507,7 +507,7 @@ describe('ToolCallGroup image hoisting', () => {
|
|||
],
|
||||
});
|
||||
|
||||
expect(screen.getByText('— Code')).toBeInTheDocument();
|
||||
expect(screen.getByText('· Code')).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Code, bash_tool/)).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('stacked-icons')).toHaveAttribute(
|
||||
'data-tool-names',
|
||||
|
|
@ -530,7 +530,7 @@ describe('ToolCallGroup image hoisting', () => {
|
|||
expect(screen.queryByText('Used 2 tools')).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('question-icon')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('stacked-icons')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/— ask_user_question/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/· ask_user_question/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('uses the present tense while a multi-question turn is still streaming', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue