From 9c5ab755c1310a12bcfe59431b4c5e0898bc5630 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Fri, 30 May 2025 19:32:49 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(playground):=20improve=20mul?= =?UTF-8?q?timodal=20content=20handling=20and=20error=20resilience?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix TypeError when processing multimodal messages containing both text and images. The error "textContent.text.trim is not a function" occurred when textContent was null or textContent.text was not a string type. Changes: - Add comprehensive type checking for textContent.text access - Implement getTextContent() utility function for unified content extraction - Enhance error handling to support multimodal content display - Fix message copy functionality to handle array-format content - Improve message reset functionality to extract text content for retry - Add user-friendly warnings when copying messages without text content Technical improvements: - Validate textContent existence and text property type before calling trim() - Extract text content from multimodal messages for operations like copy/retry - Maintain backward compatibility with string-format content - Preserve all existing functionality while adding robust error handling Fixes issues with: - Image + text message processing - Message copying from multimodal content - Message retry with image attachments - Error display for complex message formats This ensures the playground component handles multimodal content gracefully without breaking existing text-only message functionality. --- .../components/playground/MessageContent.js | 37 +++++++++++---- web/src/pages/Playground/Playground.js | 47 +++++++++++++++++-- 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/web/src/components/playground/MessageContent.js b/web/src/components/playground/MessageContent.js index 714a6550..e71971e3 100644 --- a/web/src/components/playground/MessageContent.js +++ b/web/src/components/playground/MessageContent.js @@ -15,10 +15,23 @@ const MessageContent = ({ message, className, styleState, onToggleReasoningExpan const { t } = useTranslation(); if (message.status === 'error') { + let errorText; + + if (Array.isArray(message.content)) { + const textContent = message.content.find(item => item.type === 'text'); + errorText = textContent && textContent.text && typeof textContent.text === 'string' + ? textContent.text + : t('请求发生错误'); + } else if (typeof message.content === 'string') { + errorText = message.content; + } else { + errorText = t('请求发生错误'); + } + return (
- {message.content || t('请求发生错误')} + {errorText}
); @@ -26,11 +39,23 @@ const MessageContent = ({ message, className, styleState, onToggleReasoningExpan const isThinkingStatus = message.status === 'loading' || message.status === 'incomplete'; let currentExtractedThinkingContent = null; - let currentDisplayableFinalContent = message.content || ""; + let currentDisplayableFinalContent = ""; let thinkingSource = null; + const getTextContent = (content) => { + if (Array.isArray(content)) { + const textItem = content.find(item => item.type === 'text'); + return textItem && textItem.text && typeof textItem.text === 'string' ? textItem.text : ''; + } else if (typeof content === 'string') { + return content; + } + return ''; + }; + + currentDisplayableFinalContent = getTextContent(message.content); + if (message.role === 'assistant') { - let baseContentForDisplay = message.content || ""; + let baseContentForDisplay = getTextContent(message.content); let combinedThinkingContent = ""; if (message.reasoningContent) { @@ -175,7 +200,6 @@ const MessageContent = ({ message, className, styleState, onToggleReasoningExpan {/* 渲染消息内容 */} {(() => { - // 处理多模态内容(文本+图片) if (Array.isArray(message.content)) { const textContent = message.content.find(item => item.type === 'text'); const imageContents = message.content.filter(item => item.type === 'image_url'); @@ -209,7 +233,7 @@ const MessageContent = ({ message, className, styleState, onToggleReasoningExpan )} {/* 显示文本内容 */} - {textContent && textContent.text && textContent.text.trim() !== '' && ( + {textContent && textContent.text && typeof textContent.text === 'string' && textContent.text.trim() !== '' && (
@@ -218,10 +242,8 @@ const MessageContent = ({ message, className, styleState, onToggleReasoningExpan ); } - // 处理纯文本内容或助手回复 if (typeof message.content === 'string') { if (message.role === 'assistant') { - // 助手回复使用处理后的内容 if (finalDisplayableFinalContent && finalDisplayableFinalContent.trim() !== '') { return (
@@ -230,7 +252,6 @@ const MessageContent = ({ message, className, styleState, onToggleReasoningExpan ); } } else { - // 用户文本消息 return (
diff --git a/web/src/pages/Playground/Playground.js b/web/src/pages/Playground/Playground.js index db3d8938..661b0db9 100644 --- a/web/src/pages/Playground/Playground.js +++ b/web/src/pages/Playground/Playground.js @@ -712,18 +712,41 @@ const Playground = () => { const handleMessageCopy = useCallback((message) => { if (!message.content) return; + let textToCopy; + + if (Array.isArray(message.content)) { + const textContent = message.content.find(item => item.type === 'text'); + if (textContent && textContent.text && typeof textContent.text === 'string') { + textToCopy = textContent.text; + } else { + Toast.warning({ + content: t('此消息没有可复制的文本内容'), + duration: 2, + }); + return; + } + } else if (typeof message.content === 'string') { + textToCopy = message.content; + } else { + Toast.warning({ + content: t('无法复制此类型的消息内容'), + duration: 2, + }); + return; + } + if (navigator.clipboard && navigator.clipboard.writeText) { - navigator.clipboard.writeText(message.content).then(() => { + navigator.clipboard.writeText(textToCopy).then(() => { Toast.success({ content: t('消息已复制到剪贴板'), duration: 2, }); }).catch(err => { console.error('Clipboard API 复制失败:', err); - fallbackCopyToClipboard(message.content); + fallbackCopyToClipboard(textToCopy); }); } else { - fallbackCopyToClipboard(message.content); + fallbackCopyToClipboard(textToCopy); } }, [t]); @@ -790,7 +813,14 @@ const Playground = () => { if (targetMessage.role === 'user') { const newMessages = prevMessages.slice(0, messageIndex); setTimeout(() => { - onMessageSend(targetMessage.content); + let contentToSend; + if (Array.isArray(targetMessage.content)) { + const textContent = targetMessage.content.find(item => item.type === 'text'); + contentToSend = textContent && textContent.text ? textContent.text : ''; + } else { + contentToSend = targetMessage.content; + } + onMessageSend(contentToSend); }, 100); return newMessages; } else if (targetMessage.role === 'assistant') { @@ -802,7 +832,14 @@ const Playground = () => { const userMessage = prevMessages[userMessageIndex]; const newMessages = prevMessages.slice(0, userMessageIndex); setTimeout(() => { - onMessageSend(userMessage.content); + let contentToSend; + if (Array.isArray(userMessage.content)) { + const textContent = userMessage.content.find(item => item.type === 'text'); + contentToSend = textContent && textContent.text ? textContent.text : ''; + } else { + contentToSend = userMessage.content; + } + onMessageSend(contentToSend); }, 100); return newMessages; }