mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-08 06:19:05 +08:00
fix: cannot paste images when mentioned visual models (#7817)
* refactor(paste): 优化粘贴功能逻辑,移除模型类型依赖 重构粘贴服务处理逻辑,将文件类型支持判断移至组件层 简化handlePaste接口,移除isVisionModel和isGenerateImageModel参数 * fix(MessageEditor): 支持生成图片模型的视觉消息检查 * refactor(Inputbar): 移除调试用的console.log语句 * refactor(PasteService): 移除调试用的console.log语句
This commit is contained in:
parent
6a4468193b
commit
1493132974
@ -521,8 +521,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
async (event: ClipboardEvent) => {
|
async (event: ClipboardEvent) => {
|
||||||
return await PasteService.handlePaste(
|
return await PasteService.handlePaste(
|
||||||
event,
|
event,
|
||||||
isVisionModel(model),
|
|
||||||
isGenerateImageModel(model),
|
|
||||||
supportedExts,
|
supportedExts,
|
||||||
setFiles,
|
setFiles,
|
||||||
setText,
|
setText,
|
||||||
@ -533,7 +531,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
t
|
t
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[model, pasteLongTextAsFile, pasteLongTextThreshold, resizeTextArea, supportedExts, t, text]
|
[pasteLongTextAsFile, pasteLongTextThreshold, resizeTextArea, supportedExts, t, text]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
|
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
|||||||
@ -41,14 +41,58 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
|
|||||||
const [isFileDragging, setIsFileDragging] = useState(false)
|
const [isFileDragging, setIsFileDragging] = useState(false)
|
||||||
const { assistant } = useAssistant(message.assistantId)
|
const { assistant } = useAssistant(message.assistantId)
|
||||||
const model = assistant.model || assistant.defaultModel
|
const model = assistant.model || assistant.defaultModel
|
||||||
const isVision = useMemo(() => isVisionModel(model), [model])
|
|
||||||
const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision])
|
|
||||||
const { pasteLongTextThreshold, fontSize, sendMessageShortcut, enableSpellCheck } = useSettings()
|
const { pasteLongTextThreshold, fontSize, sendMessageShortcut, enableSpellCheck } = useSettings()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const textareaRef = useRef<TextAreaRef>(null)
|
const textareaRef = useRef<TextAreaRef>(null)
|
||||||
const attachmentButtonRef = useRef<AttachmentButtonRef>(null)
|
const attachmentButtonRef = useRef<AttachmentButtonRef>(null)
|
||||||
const isUserMessage = message.role === 'user'
|
const isUserMessage = message.role === 'user'
|
||||||
|
|
||||||
|
const topicMessages = useAppSelector((state) => selectMessagesForTopic(state, topicId))
|
||||||
|
|
||||||
|
const couldAddImageFile = useMemo(() => {
|
||||||
|
const relatedAssistantMessages = topicMessages.filter((m) => m.askId === message.id && m.role === 'assistant')
|
||||||
|
if (relatedAssistantMessages.length === 0) {
|
||||||
|
// 无关联消息时fallback到助手模型
|
||||||
|
return isVisionModel(model)
|
||||||
|
}
|
||||||
|
return relatedAssistantMessages.every((m) => {
|
||||||
|
if (m.model) {
|
||||||
|
return isVisionModel(m.model) || isGenerateImageModel(m.model)
|
||||||
|
} else {
|
||||||
|
// 若消息关联不存在的模型,视为其支持视觉
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [message.id, model, topicMessages])
|
||||||
|
|
||||||
|
const couldAddTextFile = useMemo(() => {
|
||||||
|
const relatedAssistantMessages = topicMessages.filter((m) => m.askId === message.id && m.role === 'assistant')
|
||||||
|
if (relatedAssistantMessages.length === 0) {
|
||||||
|
// 无关联消息时fallback到助手模型
|
||||||
|
return isVisionModel(model) || (!isVisionModel(model) && !isGenerateImageModel(model))
|
||||||
|
}
|
||||||
|
return relatedAssistantMessages.every((m) => {
|
||||||
|
if (m.model) {
|
||||||
|
return isVisionModel(m.model) || (!isVisionModel(m.model) && !isGenerateImageModel(m.model))
|
||||||
|
} else {
|
||||||
|
// 若消息关联不存在的模型,视为其支持文本
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [message.id, model, topicMessages])
|
||||||
|
|
||||||
|
const extensions = useMemo(() => {
|
||||||
|
if (couldAddImageFile && couldAddTextFile) {
|
||||||
|
return [...imageExts, ...documentExts, ...textExts]
|
||||||
|
} else if (couldAddImageFile) {
|
||||||
|
return [...imageExts]
|
||||||
|
} else if (couldAddTextFile) {
|
||||||
|
return [...documentExts, ...textExts]
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}, [couldAddImageFile, couldAddTextFile])
|
||||||
|
|
||||||
const resizeTextArea = useCallback(() => {
|
const resizeTextArea = useCallback(() => {
|
||||||
const textArea = textareaRef.current?.resizableTextArea?.textArea
|
const textArea = textareaRef.current?.resizableTextArea?.textArea
|
||||||
if (textArea) {
|
if (textArea) {
|
||||||
@ -70,9 +114,7 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
|
|||||||
async (event: ClipboardEvent) => {
|
async (event: ClipboardEvent) => {
|
||||||
return await PasteService.handlePaste(
|
return await PasteService.handlePaste(
|
||||||
event,
|
event,
|
||||||
isVisionModel(model),
|
extensions,
|
||||||
isGenerateImageModel(model),
|
|
||||||
supportExts,
|
|
||||||
setFiles,
|
setFiles,
|
||||||
undefined, // 不需要setText
|
undefined, // 不需要setText
|
||||||
false, // 不需要 pasteLongTextAsFile
|
false, // 不需要 pasteLongTextAsFile
|
||||||
@ -82,7 +124,7 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
|
|||||||
t
|
t
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[model, pasteLongTextThreshold, resizeTextArea, supportExts, t]
|
[extensions, pasteLongTextThreshold, resizeTextArea, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
// 添加全局粘贴事件处理
|
// 添加全局粘贴事件处理
|
||||||
@ -125,7 +167,7 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
|
|||||||
if (files) {
|
if (files) {
|
||||||
let supportedFiles = 0
|
let supportedFiles = 0
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
if (supportExts.includes(getFileExtension(file.path))) {
|
if (extensions.includes(getFileExtension(file.path))) {
|
||||||
setFiles((prevFiles) => [...prevFiles, file])
|
setFiles((prevFiles) => [...prevFiles, file])
|
||||||
supportedFiles++
|
supportedFiles++
|
||||||
}
|
}
|
||||||
@ -209,52 +251,6 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const topicMessages = useAppSelector((state) => selectMessagesForTopic(state, topicId))
|
|
||||||
|
|
||||||
const couldAddImageFile = useMemo(() => {
|
|
||||||
const relatedAssistantMessages = topicMessages.filter((m) => m.askId === message.id && m.role === 'assistant')
|
|
||||||
if (relatedAssistantMessages.length === 0) {
|
|
||||||
// 无关联消息时fallback到助手模型
|
|
||||||
return isVisionModel(model)
|
|
||||||
}
|
|
||||||
return relatedAssistantMessages.every((m) => {
|
|
||||||
if (m.model) {
|
|
||||||
return isVisionModel(m.model)
|
|
||||||
} else {
|
|
||||||
// 若消息关联不存在的模型,视为其支持视觉
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [message.id, model, topicMessages])
|
|
||||||
|
|
||||||
const couldAddTextFile = useMemo(() => {
|
|
||||||
const relatedAssistantMessages = topicMessages.filter((m) => m.askId === message.id && m.role === 'assistant')
|
|
||||||
if (relatedAssistantMessages.length === 0) {
|
|
||||||
// 无关联消息时fallback到助手模型
|
|
||||||
return isVisionModel(model) || (!isVisionModel(model) && !isGenerateImageModel(model))
|
|
||||||
}
|
|
||||||
return relatedAssistantMessages.every((m) => {
|
|
||||||
if (m.model) {
|
|
||||||
return isVisionModel(m.model) || (!isVisionModel(m.model) && !isGenerateImageModel(m.model))
|
|
||||||
} else {
|
|
||||||
// 若消息关联不存在的模型,视为其支持文本
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [message.id, model, topicMessages])
|
|
||||||
|
|
||||||
const extensions = useMemo(() => {
|
|
||||||
if (couldAddImageFile && couldAddTextFile) {
|
|
||||||
return [...imageExts, ...documentExts, ...textExts]
|
|
||||||
} else if (couldAddImageFile) {
|
|
||||||
return [...imageExts]
|
|
||||||
} else if (couldAddTextFile) {
|
|
||||||
return [...documentExts, ...textExts]
|
|
||||||
} else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}, [couldAddImageFile, couldAddTextFile])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EditorContainer className="message-editor" onDragOver={(e) => e.preventDefault()} onDrop={handleDrop}>
|
<EditorContainer className="message-editor" onDragOver={(e) => e.preventDefault()} onDrop={handleDrop}>
|
||||||
|
|||||||
@ -24,8 +24,6 @@ let isInitialized = false
|
|||||||
*/
|
*/
|
||||||
export const handlePaste = async (
|
export const handlePaste = async (
|
||||||
event: ClipboardEvent,
|
event: ClipboardEvent,
|
||||||
isVisionModel: boolean,
|
|
||||||
isGenerateImageModel: boolean,
|
|
||||||
supportExts: string[],
|
supportExts: string[],
|
||||||
setFiles: (updater: (prevFiles: FileMetadata[]) => FileMetadata[]) => void,
|
setFiles: (updater: (prevFiles: FileMetadata[]) => FileMetadata[]) => void,
|
||||||
setText?: (text: string) => void,
|
setText?: (text: string) => void,
|
||||||
@ -57,7 +55,6 @@ export const handlePaste = async (
|
|||||||
// 短文本走默认粘贴行为,直接返回
|
// 短文本走默认粘贴行为,直接返回
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 文件/图片粘贴(仅在无文本时处理)
|
// 2. 文件/图片粘贴(仅在无文本时处理)
|
||||||
if (event.clipboardData?.files && event.clipboardData.files.length > 0) {
|
if (event.clipboardData?.files && event.clipboardData.files.length > 0) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@ -69,7 +66,7 @@ export const handlePaste = async (
|
|||||||
// 如果没有路径,可能是剪贴板中的图像数据
|
// 如果没有路径,可能是剪贴板中的图像数据
|
||||||
if (!filePath) {
|
if (!filePath) {
|
||||||
// 图像生成也支持图像编辑
|
// 图像生成也支持图像编辑
|
||||||
if (file.type.startsWith('image/') && (isVisionModel || isGenerateImageModel)) {
|
if (file.type.startsWith('image/') && supportExts.includes(getFileExtension(file.name))) {
|
||||||
const tempFilePath = await window.api.file.createTempFile(file.name)
|
const tempFilePath = await window.api.file.createTempFile(file.name)
|
||||||
const arrayBuffer = await file.arrayBuffer()
|
const arrayBuffer = await file.arrayBuffer()
|
||||||
const uint8Array = new Uint8Array(arrayBuffer)
|
const uint8Array = new Uint8Array(arrayBuffer)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user