diff --git a/src/components/ChatMessage.tsx b/src/components/ChatMessage.tsx index 9ed5c98..ae4dfc5 100644 --- a/src/components/ChatMessage.tsx +++ b/src/components/ChatMessage.tsx @@ -10,7 +10,7 @@ import { CodeBlock } from './CodeBlock'; import { ToolCall } from './ToolCall'; import { ImageBlock } from './ImageBlock'; import { buildImageSrc } from '../lib/image'; -import { Bot, User, Wrench, Copy, Check, CheckCheck, RefreshCw, Zap, Info, Webhook, Braces, Clock, AlertCircle, Bookmark } from 'lucide-react'; +import { Bot, User, Wrench, Copy, Check, CheckCheck, RefreshCw, Zap, Info, Webhook, Braces, Clock, AlertCircle, Bookmark, ChevronDown } from 'lucide-react'; import { t, getLocale } from '../lib/i18n'; import { useLocale } from '../hooks/useLocale'; import { stripWebhookScaffolding, hasWebhookScaffolding } from '../lib/systemEvent'; @@ -199,6 +199,49 @@ function MarkdownLink(props: React.AnchorHTMLAttributes) { const markdownComponents = { pre: CodeBlock, img: MarkdownImage, a: MarkdownLink }; +/** Threshold (in characters) above which assistant messages are collapsed by default */ +const COLLAPSE_THRESHOLD = 3000; +const COLLAPSED_MAX_HEIGHT = 400; // px + +/** Wrapper that collapses long content with a gradient fade and "Show more" button */ +function CollapsibleContent({ content, isStreaming, children }: { content: string; isStreaming?: boolean; children: React.ReactNode }) { + const [expanded, setExpanded] = useState(false); + const shouldCollapse = !isStreaming && content.length > COLLAPSE_THRESHOLD; + + if (!shouldCollapse || expanded) { + return ( + <> + {children} + {shouldCollapse && expanded && ( + + )} + + ); + } + + return ( +
+
+ {children} +
+
+ +
+ ); +} + function renderTextBlocks(blocks: MessageBlock[]) { return getTextBlocks(blocks).map((block, i) => (
@@ -516,12 +559,24 @@ export const ChatMessageComponent = memo(function ChatMessageComponent({ message )} {/* User-visible text */} - {message.blocks.length > 0 ? renderTextBlocks(message.blocks) : ( -
- - {autoFormatText(message.content)} - -
+ {!isUser ? ( + + {message.blocks.length > 0 ? renderTextBlocks(message.blocks) : ( +
+ + {autoFormatText(message.content)} + +
+ )} +
+ ) : ( + message.blocks.length > 0 ? renderTextBlocks(message.blocks) : ( +
+ + {autoFormatText(message.content)} + +
+ ) )} {/* Inline images */} diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts index 7870871..c672e7c 100644 --- a/src/lib/i18n.ts +++ b/src/lib/i18n.ts @@ -91,6 +91,8 @@ const en = { 'message.copy': 'Copy message', 'message.copied': 'Copied!', 'message.retry': 'Resend message', + 'message.showMore': 'Show more', + 'message.showLess': 'Show less', 'message.metadata': 'Message details', 'message.rawJson': 'Raw JSON', 'message.hideRawJson': 'Hide raw JSON', @@ -255,6 +257,8 @@ const fr: Record = { 'message.copy': 'Copier le message', 'message.copied': 'Copié !', 'message.retry': 'Renvoyer le message', + 'message.showMore': 'Afficher plus', + 'message.showLess': 'Afficher moins', 'message.metadata': 'Détails du message', 'message.rawJson': 'JSON brut', 'message.hideRawJson': 'Masquer le JSON brut', @@ -411,6 +415,8 @@ const es: Record = { 'message.copy': 'Copiar mensaje', 'message.copied': '¡Copiado!', 'message.retry': 'Reenviar mensaje', + 'message.showMore': 'Ver más', + 'message.showLess': 'Ver menos', 'message.metadata': 'Detalles del mensaje', 'message.rawJson': 'JSON sin formato', 'message.hideRawJson': 'Ocultar JSON sin formato', @@ -569,6 +575,8 @@ const de: Record = { 'message.copy': 'Nachricht kopieren', 'message.copied': 'Kopiert!', 'message.retry': 'Nachricht erneut senden', + 'message.showMore': 'Mehr anzeigen', + 'message.showLess': 'Weniger anzeigen', 'message.metadata': 'Nachrichtendetails', 'message.rawJson': 'Roh-JSON', 'message.hideRawJson': 'Roh-JSON ausblenden', @@ -725,6 +733,8 @@ const ja: Record = { 'message.copy': 'メッセージをコピー', 'message.copied': 'コピーしました!', 'message.retry': 'メッセージを再送信', + 'message.showMore': 'もっと見る', + 'message.showLess': '折りたたむ', 'message.metadata': 'メッセージの詳細', 'message.rawJson': '生JSON', 'message.hideRawJson': '生JSONを非表示', @@ -881,6 +891,8 @@ const pt: Record = { 'message.copy': 'Copiar mensagem', 'message.copied': 'Copiado!', 'message.retry': 'Reenviar mensagem', + 'message.showMore': 'Ver mais', + 'message.showLess': 'Ver menos', 'message.metadata': 'Detalhes da mensagem', 'message.rawJson': 'JSON bruto', 'message.hideRawJson': 'Ocultar JSON bruto', @@ -1037,6 +1049,8 @@ const zh: Record = { 'message.copy': '复制消息', 'message.copied': '已复制!', 'message.retry': '重新发送', + 'message.showMore': '展开更多', + 'message.showLess': '收起', 'message.metadata': '消息详情', 'message.rawJson': '原始 JSON', 'message.hideRawJson': '隐藏原始 JSON', @@ -1193,6 +1207,8 @@ const it: Record = { 'message.copy': 'Copia messaggio', 'message.copied': 'Copiato!', 'message.retry': 'Reinvia messaggio', + 'message.showMore': 'Mostra di più', + 'message.showLess': 'Mostra meno', 'message.metadata': 'Dettagli messaggio', 'message.rawJson': 'JSON grezzo', 'message.hideRawJson': 'Nascondi JSON grezzo',