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

View File

@ -112,6 +112,14 @@ const MessageMenubar: FC<Props> = (props) => {
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)) {
const processedMessage = withMessageThought(clone(message))
textToEdit = processedMessage.content
@ -135,8 +143,40 @@ const MessageMenubar: FC<Props> = (props) => {
})
if (editedText && editedText !== textToEdit) {
await editMessage(message.id, { content: editedText })
resendMessage && handleResendUserMessage({ ...message, content: editedText })
// 解析编辑后的文本,提取图片 URL
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])