feat: Add image support for Claude, OpenAI and Google (#268)

* feat: Add image support for Claude
* feat: Add images support for OpenAI
* feat: Add support images for google ai
* refactor: Separate ImageComponent
* feat: Add attach image button
* feat: Add support image for Mistral provider
* feat: Add support images for OpenAI compatible providers
* feat: Add support images for Ollama
This commit is contained in:
Petr Mironychev
2025-11-20 15:49:39 +01:00
committed by GitHub
parent ce9e2717d6
commit 55b6080273
41 changed files with 860 additions and 93 deletions

View File

@ -170,6 +170,8 @@ ChatRootView {
width: parent.width
msgModel: root.chatModel.processMessageContent(model.content)
messageAttachments: model.attachments
messageImages: model.images
chatFilePath: root.chatFilePath()
isUserMessage: model.roleType === ChatModel.User
messageIndex: index
textFontFamily: root.textFontFamily
@ -394,6 +396,7 @@ ChatRootView {
onCheckedChanged: root.setIsSyncOpenFiles(bottomBar.syncOpenFiles.checked)
}
attachFiles.onClicked: root.showAttachFilesDialog()
attachImages.onClicked: root.showAddImageDialog()
linkFiles.onClicked: root.showLinkFilesDialog()
}
}

View File

@ -28,6 +28,8 @@ Rectangle {
property alias msgModel: msgCreator.model
property alias messageAttachments: attachmentsModel.model
property alias messageImages: imagesModel.model
property string chatFilePath: ""
property string textFontFamily: Qt.application.font.family
property string codeFontFamily: {
switch (Qt.platform.os) {
@ -140,6 +142,27 @@ Rectangle {
}
}
}
Flow {
id: imagesFlow
Layout.fillWidth: true
visible: imagesModel.model && imagesModel.model.length > 0
leftPadding: 10
rightPadding: 10
spacing: 10
Repeater {
id: imagesModel
delegate: ImageComponent {
required property int index
required property var modelData
itemData: modelData
}
}
}
}
Rectangle {
@ -215,4 +238,79 @@ Rectangle {
codeFontFamily: root.codeFontFamily
codeFontSize: root.codeFontSize
}
component ImageComponent : Rectangle {
required property var itemData
readonly property int maxImageWidth: Math.min(400, root.width - 40)
readonly property int maxImageHeight: 300
width: Math.min(imageDisplay.implicitWidth, maxImageWidth) + 16
height: imageDisplay.implicitHeight + fileNameText.implicitHeight + 16
radius: 4
color: palette.base
border.width: 1
border.color: palette.mid
ColumnLayout {
anchors.fill: parent
anchors.margins: 8
spacing: 4
Image {
id: imageDisplay
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: parent.parent.maxImageWidth
Layout.maximumHeight: parent.parent.maxImageHeight
source: {
if (!itemData.storedPath || !root.chatFilePath) return "";
var fileInfo = chatFileInfo(root.chatFilePath);
var imagesFolder = fileInfo.dir + "/" + fileInfo.baseName + "_images";
return "file://" + imagesFolder + "/" + itemData.storedPath;
}
sourceSize.width: parent.parent.maxImageWidth
sourceSize.height: parent.parent.maxImageHeight
fillMode: Image.PreserveAspectFit
cache: true
asynchronous: true
smooth: true
mipmap: true
BusyIndicator {
anchors.centerIn: parent
running: imageDisplay.status === Image.Loading
visible: running
}
Text {
anchors.centerIn: parent
text: qsTr("Failed to load image")
visible: imageDisplay.status === Image.Error
color: palette.placeholderText
}
}
Text {
id: fileNameText
Layout.fillWidth: true
text: itemData.fileName || ""
color: palette.text
font.pointSize: root.textFontSize - 1
elide: Text.ElideMiddle
horizontalAlignment: Text.AlignHCenter
}
}
}
function chatFileInfo(filePath) {
if (!filePath) return {dir: "", baseName: ""};
var lastSlash = filePath.lastIndexOf("/");
var dir = lastSlash >= 0 ? filePath.substring(0, lastSlash) : "";
var fileName = lastSlash >= 0 ? filePath.substring(lastSlash + 1) : filePath;
var lastDot = fileName.lastIndexOf(".");
var baseName = lastDot >= 0 ? fileName.substring(0, lastDot) : fileName;
return {dir: dir, baseName: baseName};
}
}

View File

@ -29,9 +29,9 @@ Rectangle {
property alias sendButton: sendButtonId
property alias syncOpenFiles: syncOpenFilesId
property alias attachFiles: attachFilesId
property alias attachImages: attachImagesId
property alias linkFiles: linkFilesId
color: palette.window.hslLightness > 0.5 ?
Qt.darker(palette.window, 1.1) :
Qt.lighter(palette.window, 1.1)
@ -73,6 +73,19 @@ Rectangle {
ToolTip.text: qsTr("Attach file to message")
}
QoAButton {
id: attachImagesId
icon {
source: "qrc:/qt/qml/ChatView/icons/image-dark.svg"
height: 15
width: 15
}
ToolTip.visible: hovered
ToolTip.delay: 250
ToolTip.text: qsTr("Attach image to message")
}
QoAButton {
id: linkFilesId