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:
Phantom 2025-07-14 11:41:54 +08:00 committed by GitHub
parent 6a4468193b
commit 1493132974
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 51 additions and 60 deletions

View File

@ -521,8 +521,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
async (event: ClipboardEvent) => {
return await PasteService.handlePaste(
event,
isVisionModel(model),
isGenerateImageModel(model),
supportedExts,
setFiles,
setText,
@ -533,7 +531,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
t
)
},
[model, pasteLongTextAsFile, pasteLongTextThreshold, resizeTextArea, supportedExts, t, text]
[pasteLongTextAsFile, pasteLongTextThreshold, resizeTextArea, supportedExts, t, text]
)
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {

View File

@ -41,14 +41,58 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
const [isFileDragging, setIsFileDragging] = useState(false)
const { assistant } = useAssistant(message.assistantId)
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 { t } = useTranslation()
const textareaRef = useRef<TextAreaRef>(null)
const attachmentButtonRef = useRef<AttachmentButtonRef>(null)
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 textArea = textareaRef.current?.resizableTextArea?.textArea
if (textArea) {
@ -70,9 +114,7 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
async (event: ClipboardEvent) => {
return await PasteService.handlePaste(
event,
isVisionModel(model),
isGenerateImageModel(model),
supportExts,
extensions,
setFiles,
undefined, // 不需要setText
false, // 不需要 pasteLongTextAsFile
@ -82,7 +124,7 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
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) {
let supportedFiles = 0
files.forEach((file) => {
if (supportExts.includes(getFileExtension(file.path))) {
if (extensions.includes(getFileExtension(file.path))) {
setFiles((prevFiles) => [...prevFiles, file])
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 (
<>
<EditorContainer className="message-editor" onDragOver={(e) => e.preventDefault()} onDrop={handleDrop}>

View File

@ -24,8 +24,6 @@ let isInitialized = false
*/
export const handlePaste = async (
event: ClipboardEvent,
isVisionModel: boolean,
isGenerateImageModel: boolean,
supportExts: string[],
setFiles: (updater: (prevFiles: FileMetadata[]) => FileMetadata[]) => void,
setText?: (text: string) => void,
@ -57,7 +55,6 @@ export const handlePaste = async (
// 短文本走默认粘贴行为,直接返回
return false
}
// 2. 文件/图片粘贴(仅在无文本时处理)
if (event.clipboardData?.files && event.clipboardData.files.length > 0) {
event.preventDefault()
@ -69,7 +66,7 @@ export const handlePaste = async (
// 如果没有路径,可能是剪贴板中的图像数据
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 arrayBuffer = await file.arrayBuffer()
const uint8Array = new Uint8Array(arrayBuffer)