- 0 && 'result-streaming',
- isCreatedByUser && !enableUserMsgMarkdown && 'whitespace-pre-wrap',
- 'text-text-primary',
- )}
- >
- {content}
-
+
+ 0 && 'result-streaming',
+ isCreatedByUser && !enableUserMsgMarkdown && 'whitespace-pre-wrap',
+ 'text-text-primary',
+ )}
+ >
+ {content}
+
+
);
};
diff --git a/client/src/components/Chat/Messages/Content/Parts/CollapsibleText.tsx b/client/src/components/Chat/Messages/Content/Parts/CollapsibleText.tsx
new file mode 100644
index 0000000000..7935ddccde
--- /dev/null
+++ b/client/src/components/Chat/Messages/Content/Parts/CollapsibleText.tsx
@@ -0,0 +1,131 @@
+import { useLayoutEffect, memo, useId, useRef, useState } from 'react';
+import { Button } from '@librechat/client';
+import { ChevronDown, ChevronUp } from 'lucide-react';
+import type { ReactNode } from 'react';
+import { useLocalize } from '~/hooks';
+import { cn } from '~/utils';
+
+/** Collapsed preview height (px) for a long message before "Show more". The
+ * tolerance below only decides whether the toggle appears: it absorbs the
+ * trailing markdown margin so content that fits but for its own bottom margin
+ * does not trip a pointless toggle. The clamp itself is applied only once the
+ * content actually overflows, so nothing is ever hidden without a toggle. */
+const COLLAPSED_MAX_HEIGHT = 256;
+const OVERFLOW_TOLERANCE = 8;
+
+/**
+ * Clamps a long user message to a preview height with a fade and a
+ * "Show more" toggle, so a pasted wall of text or code cannot dominate the
+ * thread. The clamp is visual only: `overflow-hidden` keeps the full text in
+ * the DOM, so it stays readable by assistive tech, copyable, and findable by
+ * in-page search. Renders children untouched while `enabled` is false, keeping
+ * the DOM identical when the preference is off.
+ */
+const CollapsibleText = memo(function CollapsibleText({
+ enabled,
+ children,
+}: {
+ enabled: boolean;
+ children: ReactNode;
+}) {
+ const localize = useLocalize();
+ const contentRef = useRef