fix: 解决聊天页面图片复制失败的问题和点击编辑回复的时候,不显示图片url的问题 (#4496)

* fix: 解决聊天页面图片复制失败的问题和点击编辑回复的时候,不显示图片url的问题

* fix: 解决chat模式,gemini-2.0-flash-exp-image-generation返回base64图片,无法复制的问题

* fix(MessageImage): Update the image copying feature to process base64 and URL formatted images based on their type

---------

Co-authored-by: magicdmer <magicdmer@163.com>
Co-authored-by: eeee0717 <chentao020717Work@outlook.com>
This commit is contained in:
magicdmer 2025-04-09 16:23:25 +08:00 committed by GitHub
parent 10efa444bf
commit f7f7d2bde8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 81 additions and 25 deletions

View File

@ -36,35 +36,51 @@ const MessageImage: FC<Props> = ({ message }) => {
} }
} }
// 复制 base64 图片到剪贴板 // 复制图片到剪贴板
const onCopy = async (imageBase64: string) => { const onCopy = async (type: string, image: string) => {
try { try {
const base64Data = imageBase64.split(',')[1] switch (type) {
const mimeType = imageBase64.split(';')[0].split(':')[1] case 'base64': {
// 处理 base64 格式的图片
const parts = image.split(';base64,')
if (parts.length === 2) {
const mimeType = parts[0].replace('data:', '')
const base64Data = parts[1]
const byteCharacters = atob(base64Data)
const byteArrays: Uint8Array[] = []
const byteCharacters = atob(base64Data) for (let offset = 0; offset < byteCharacters.length; offset += 512) {
const byteArrays: Uint8Array[] = [] const slice = byteCharacters.slice(offset, offset + 512)
const byteNumbers = new Array(slice.length)
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i)
}
const byteArray = new Uint8Array(byteNumbers)
byteArrays.push(byteArray)
}
for (let i = 0; i < byteCharacters.length; i += 512) { const blob = new Blob(byteArrays, { type: mimeType })
const slice = byteCharacters.slice(i, i + 512) await navigator.clipboard.write([new ClipboardItem({ [mimeType]: blob })])
} else {
const byteNumbers = new Array(slice.length) throw new Error('无效的 base64 图片格式')
for (let j = 0; j < slice.length; j++) { }
byteNumbers[j] = slice.charCodeAt(j) break
} }
case 'url':
{
// 处理 URL 格式的图片
const response = await fetch(image)
const blob = await response.blob()
const byteArray = new Uint8Array(byteNumbers) await navigator.clipboard.write([
byteArrays.push(byteArray) new ClipboardItem({
[blob.type]: blob
})
])
}
break
} }
const blob = new Blob(byteArrays, { type: mimeType })
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob
})
])
window.message.success(t('message.copy.success')) window.message.success(t('message.copy.success'))
} catch (error) { } catch (error) {
console.error('复制图片失败:', error) console.error('复制图片失败:', error)
@ -95,7 +111,7 @@ const MessageImage: FC<Props> = ({ message }) => {
<ZoomOutOutlined disabled={scale === 1} onClick={onZoomOut} /> <ZoomOutOutlined disabled={scale === 1} onClick={onZoomOut} />
<ZoomInOutlined disabled={scale === 50} onClick={onZoomIn} /> <ZoomInOutlined disabled={scale === 50} onClick={onZoomIn} />
<UndoOutlined onClick={onReset} /> <UndoOutlined onClick={onReset} />
<CopyOutlined onClick={() => onCopy(image)} /> <CopyOutlined onClick={() => onCopy(message.metadata?.generateImage?.type!, image)} />
<DownloadOutlined onClick={() => onDownload(image, index)} /> <DownloadOutlined onClick={() => onDownload(image, index)} />
</ToobarWrapper> </ToobarWrapper>
) )

View File

@ -112,6 +112,14 @@ const MessageMenubar: FC<Props> = (props) => {
let textToEdit = message.content let textToEdit = message.content
// 如果是包含图片的消息,添加图片的 markdown 格式
if (message.metadata?.generateImage?.images) {
const imageMarkdown = message.metadata.generateImage.images
.map((image, index) => `![image-${index}](${image})`)
.join('\n')
textToEdit = `${textToEdit}\n\n${imageMarkdown}`
}
if (message.role === 'assistant' && message.model && isReasoningModel(message.model)) { if (message.role === 'assistant' && message.model && isReasoningModel(message.model)) {
const processedMessage = withMessageThought(clone(message)) const processedMessage = withMessageThought(clone(message))
textToEdit = processedMessage.content textToEdit = processedMessage.content
@ -135,8 +143,40 @@ const MessageMenubar: FC<Props> = (props) => {
}) })
if (editedText && editedText !== textToEdit) { if (editedText && editedText !== textToEdit) {
await editMessage(message.id, { content: editedText }) // 解析编辑后的文本,提取图片 URL
resendMessage && handleResendUserMessage({ ...message, content: editedText }) const imageRegex = /!\[image-\d+\]\((.*?)\)/g
const imageUrls: string[] = []
let match
let content = editedText
while ((match = imageRegex.exec(editedText)) !== null) {
imageUrls.push(match[1])
content = content.replace(match[0], '')
}
// 更新消息内容,保留图片信息
await editMessage(message.id, {
content: content.trim(),
metadata: {
...message.metadata,
generateImage: imageUrls.length > 0 ? {
type: 'url',
images: imageUrls
} : undefined
}
})
resendMessage && handleResendUserMessage({
...message,
content: content.trim(),
metadata: {
...message.metadata,
generateImage: imageUrls.length > 0 ? {
type: 'url',
images: imageUrls
} : undefined
}
})
} }
}, [message, editMessage, handleResendUserMessage, t]) }, [message, editMessage, handleResendUserMessage, t])