mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-02-16 03:53:02 -05:00
refactor: Reworking attaching files (#280)
chore: Upgrade chat format version
This commit is contained in:
@ -78,11 +78,26 @@ QVariant ChatModel::data(const QModelIndex &index, int role) const
|
|||||||
return message.content;
|
return message.content;
|
||||||
}
|
}
|
||||||
case Roles::Attachments: {
|
case Roles::Attachments: {
|
||||||
QStringList filenames;
|
QVariantList attachmentsList;
|
||||||
for (const auto &attachment : message.attachments) {
|
for (const auto &attachment : message.attachments) {
|
||||||
filenames << attachment.filename;
|
QVariantMap attachmentMap;
|
||||||
|
attachmentMap["fileName"] = attachment.filename;
|
||||||
|
attachmentMap["storedPath"] = attachment.content;
|
||||||
|
|
||||||
|
if (!m_chatFilePath.isEmpty()) {
|
||||||
|
QFileInfo fileInfo(m_chatFilePath);
|
||||||
|
QString baseName = fileInfo.completeBaseName();
|
||||||
|
QString dirPath = fileInfo.absolutePath();
|
||||||
|
QString contentFolder = QDir(dirPath).filePath(baseName + "_content");
|
||||||
|
QString fullPath = QDir(contentFolder).filePath(attachment.content);
|
||||||
|
attachmentMap["filePath"] = fullPath;
|
||||||
|
} else {
|
||||||
|
attachmentMap["filePath"] = QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
attachmentsList.append(attachmentMap);
|
||||||
}
|
}
|
||||||
return filenames;
|
return attachmentsList;
|
||||||
}
|
}
|
||||||
case Roles::IsRedacted: {
|
case Roles::IsRedacted: {
|
||||||
return message.isRedacted;
|
return message.isRedacted;
|
||||||
@ -99,8 +114,8 @@ QVariant ChatModel::data(const QModelIndex &index, int role) const
|
|||||||
QFileInfo fileInfo(m_chatFilePath);
|
QFileInfo fileInfo(m_chatFilePath);
|
||||||
QString baseName = fileInfo.completeBaseName();
|
QString baseName = fileInfo.completeBaseName();
|
||||||
QString dirPath = fileInfo.absolutePath();
|
QString dirPath = fileInfo.absolutePath();
|
||||||
QString imagesFolder = QDir(dirPath).filePath(baseName + "_images");
|
QString contentFolder = QDir(dirPath).filePath(baseName + "_content");
|
||||||
QString fullPath = QDir(imagesFolder).filePath(image.storedPath);
|
QString fullPath = QDir(contentFolder).filePath(image.storedPath);
|
||||||
imageMap["imageUrl"] = QUrl::fromLocalFile(fullPath).toString();
|
imageMap["imageUrl"] = QUrl::fromLocalFile(fullPath).toString();
|
||||||
} else {
|
} else {
|
||||||
imageMap["imageUrl"] = QString();
|
imageMap["imageUrl"] = QString();
|
||||||
@ -135,15 +150,6 @@ void ChatModel::addMessage(
|
|||||||
bool isRedacted,
|
bool isRedacted,
|
||||||
const QString &signature)
|
const QString &signature)
|
||||||
{
|
{
|
||||||
QString fullContent = content;
|
|
||||||
if (!attachments.isEmpty()) {
|
|
||||||
fullContent += "\n\nAttached files list:";
|
|
||||||
for (const auto &attachment : attachments) {
|
|
||||||
fullContent += QString("\nname: %1\nfile content:\n%2")
|
|
||||||
.arg(attachment.filename, attachment.content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_messages.isEmpty() && !id.isEmpty() && m_messages.last().id == id
|
if (!m_messages.isEmpty() && !id.isEmpty() && m_messages.last().id == id
|
||||||
&& m_messages.last().role == role) {
|
&& m_messages.last().role == role) {
|
||||||
Message &lastMessage = m_messages.last();
|
Message &lastMessage = m_messages.last();
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
const QString ChatSerializer::VERSION = "0.1";
|
const QString ChatSerializer::VERSION = "0.2";
|
||||||
|
|
||||||
SerializationResult ChatSerializer::saveToFile(const ChatModel *model, const QString &filePath)
|
SerializationResult ChatSerializer::saveToFile(const ChatModel *model, const QString &filePath)
|
||||||
{
|
{
|
||||||
@ -38,11 +38,11 @@ SerializationResult ChatSerializer::saveToFile(const ChatModel *model, const QSt
|
|||||||
return {false, "Failed to create directory structure"};
|
return {false, "Failed to create directory structure"};
|
||||||
}
|
}
|
||||||
|
|
||||||
QString imagesFolder = getChatImagesFolder(filePath);
|
QString contentFolder = getChatContentFolder(filePath);
|
||||||
QDir dir;
|
QDir dir;
|
||||||
if (!dir.exists(imagesFolder)) {
|
if (!dir.exists(contentFolder)) {
|
||||||
if (!dir.mkpath(imagesFolder)) {
|
if (!dir.mkpath(contentFolder)) {
|
||||||
LOG_MESSAGE(QString("Warning: Failed to create images folder: %1").arg(imagesFolder));
|
LOG_MESSAGE(QString("Warning: Failed to create content folder: %1").arg(contentFolder));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +103,17 @@ QJsonObject ChatSerializer::serializeMessage(const ChatModel::Message &message,
|
|||||||
messageObj["signature"] = message.signature;
|
messageObj["signature"] = message.signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!message.attachments.isEmpty()) {
|
||||||
|
QJsonArray attachmentsArray;
|
||||||
|
for (const auto &attachment : message.attachments) {
|
||||||
|
QJsonObject attachmentObj;
|
||||||
|
attachmentObj["fileName"] = attachment.filename;
|
||||||
|
attachmentObj["storedPath"] = attachment.content;
|
||||||
|
attachmentsArray.append(attachmentObj);
|
||||||
|
}
|
||||||
|
messageObj["attachments"] = attachmentsArray;
|
||||||
|
}
|
||||||
|
|
||||||
if (!message.images.isEmpty()) {
|
if (!message.images.isEmpty()) {
|
||||||
QJsonArray imagesArray;
|
QJsonArray imagesArray;
|
||||||
for (const auto &image : message.images) {
|
for (const auto &image : message.images) {
|
||||||
@ -127,6 +138,17 @@ ChatModel::Message ChatSerializer::deserializeMessage(const QJsonObject &json, c
|
|||||||
message.isRedacted = json["isRedacted"].toBool(false);
|
message.isRedacted = json["isRedacted"].toBool(false);
|
||||||
message.signature = json["signature"].toString();
|
message.signature = json["signature"].toString();
|
||||||
|
|
||||||
|
if (json.contains("attachments")) {
|
||||||
|
QJsonArray attachmentsArray = json["attachments"].toArray();
|
||||||
|
for (const auto &attachmentValue : attachmentsArray) {
|
||||||
|
QJsonObject attachmentObj = attachmentValue.toObject();
|
||||||
|
Context::ContentFile attachment;
|
||||||
|
attachment.filename = attachmentObj["fileName"].toString();
|
||||||
|
attachment.content = attachmentObj["storedPath"].toString();
|
||||||
|
message.attachments.append(attachment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (json.contains("images")) {
|
if (json.contains("images")) {
|
||||||
QJsonArray imagesArray = json["images"].toArray();
|
QJsonArray imagesArray = json["images"].toArray();
|
||||||
for (const auto &imageValue : imagesArray) {
|
for (const auto &imageValue : imagesArray) {
|
||||||
@ -192,27 +214,36 @@ bool ChatSerializer::ensureDirectoryExists(const QString &filePath)
|
|||||||
|
|
||||||
bool ChatSerializer::validateVersion(const QString &version)
|
bool ChatSerializer::validateVersion(const QString &version)
|
||||||
{
|
{
|
||||||
return version == VERSION;
|
if (version == VERSION) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version == "0.1") {
|
||||||
|
LOG_MESSAGE("Loading chat from old format 0.1 - images folder structure has changed from _images to _content");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ChatSerializer::getChatImagesFolder(const QString &chatFilePath)
|
QString ChatSerializer::getChatContentFolder(const QString &chatFilePath)
|
||||||
{
|
{
|
||||||
QFileInfo fileInfo(chatFilePath);
|
QFileInfo fileInfo(chatFilePath);
|
||||||
QString baseName = fileInfo.completeBaseName();
|
QString baseName = fileInfo.completeBaseName();
|
||||||
QString dirPath = fileInfo.absolutePath();
|
QString dirPath = fileInfo.absolutePath();
|
||||||
return QDir(dirPath).filePath(baseName + "_images");
|
return QDir(dirPath).filePath(baseName + "_content");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChatSerializer::saveImageToStorage(const QString &chatFilePath,
|
bool ChatSerializer::saveContentToStorage(const QString &chatFilePath,
|
||||||
const QString &fileName,
|
const QString &fileName,
|
||||||
const QString &base64Data,
|
const QString &base64Data,
|
||||||
QString &storedPath)
|
QString &storedPath)
|
||||||
{
|
{
|
||||||
QString imagesFolder = getChatImagesFolder(chatFilePath);
|
QString contentFolder = getChatContentFolder(chatFilePath);
|
||||||
QDir dir;
|
QDir dir;
|
||||||
if (!dir.exists(imagesFolder)) {
|
if (!dir.exists(contentFolder)) {
|
||||||
if (!dir.mkpath(imagesFolder)) {
|
if (!dir.mkpath(contentFolder)) {
|
||||||
LOG_MESSAGE(QString("Failed to create images folder: %1").arg(imagesFolder));
|
LOG_MESSAGE(QString("Failed to create content folder: %1").arg(contentFolder));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,43 +256,43 @@ bool ChatSerializer::saveImageToStorage(const QString &chatFilePath,
|
|||||||
.arg(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8))
|
.arg(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8))
|
||||||
.arg(extension);
|
.arg(extension);
|
||||||
|
|
||||||
QString fullPath = QDir(imagesFolder).filePath(uniqueName);
|
QString fullPath = QDir(contentFolder).filePath(uniqueName);
|
||||||
|
|
||||||
QByteArray imageData = QByteArray::fromBase64(base64Data.toUtf8());
|
QByteArray contentData = QByteArray::fromBase64(base64Data.toUtf8());
|
||||||
QFile file(fullPath);
|
QFile file(fullPath);
|
||||||
if (!file.open(QIODevice::WriteOnly)) {
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
LOG_MESSAGE(QString("Failed to open file for writing: %1").arg(fullPath));
|
LOG_MESSAGE(QString("Failed to open file for writing: %1").arg(fullPath));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.write(imageData) == -1) {
|
if (file.write(contentData) == -1) {
|
||||||
LOG_MESSAGE(QString("Failed to write image data: %1").arg(file.errorString()));
|
LOG_MESSAGE(QString("Failed to write content data: %1").arg(file.errorString()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
storedPath = uniqueName;
|
storedPath = uniqueName;
|
||||||
LOG_MESSAGE(QString("Saved image: %1 to %2").arg(fileName, fullPath));
|
LOG_MESSAGE(QString("Saved content: %1 to %2").arg(fileName, fullPath));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ChatSerializer::loadImageFromStorage(const QString &chatFilePath, const QString &storedPath)
|
QString ChatSerializer::loadContentFromStorage(const QString &chatFilePath, const QString &storedPath)
|
||||||
{
|
{
|
||||||
QString imagesFolder = getChatImagesFolder(chatFilePath);
|
QString contentFolder = getChatContentFolder(chatFilePath);
|
||||||
QString fullPath = QDir(imagesFolder).filePath(storedPath);
|
QString fullPath = QDir(contentFolder).filePath(storedPath);
|
||||||
|
|
||||||
QFile file(fullPath);
|
QFile file(fullPath);
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
LOG_MESSAGE(QString("Failed to open image file: %1").arg(fullPath));
|
LOG_MESSAGE(QString("Failed to open content file: %1").arg(fullPath));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray imageData = file.readAll();
|
QByteArray contentData = file.readAll();
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
return imageData.toBase64();
|
return contentData.toBase64();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@ -45,13 +45,13 @@ public:
|
|||||||
static QJsonObject serializeChat(const ChatModel *model, const QString &chatFilePath);
|
static QJsonObject serializeChat(const ChatModel *model, const QString &chatFilePath);
|
||||||
static bool deserializeChat(ChatModel *model, const QJsonObject &json, const QString &chatFilePath);
|
static bool deserializeChat(ChatModel *model, const QJsonObject &json, const QString &chatFilePath);
|
||||||
|
|
||||||
// Image management
|
// Content management (images and text files)
|
||||||
static QString getChatImagesFolder(const QString &chatFilePath);
|
static QString getChatContentFolder(const QString &chatFilePath);
|
||||||
static bool saveImageToStorage(const QString &chatFilePath,
|
static bool saveContentToStorage(const QString &chatFilePath,
|
||||||
const QString &fileName,
|
const QString &fileName,
|
||||||
const QString &base64Data,
|
const QString &base64Data,
|
||||||
QString &storedPath);
|
QString &storedPath);
|
||||||
static QString loadImageFromStorage(const QString &chatFilePath, const QString &storedPath);
|
static QString loadContentFromStorage(const QString &chatFilePath, const QString &storedPath);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const QString VERSION;
|
static const QString VERSION;
|
||||||
|
|||||||
@ -88,7 +88,24 @@ void ClientInterface::sendMessage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto attachFiles = m_contextManager->getContentFiles(textFiles);
|
QList<Context::ContentFile> storedAttachments;
|
||||||
|
if (!textFiles.isEmpty() && !m_chatFilePath.isEmpty()) {
|
||||||
|
auto attachFiles = m_contextManager->getContentFiles(textFiles);
|
||||||
|
for (const auto &file : attachFiles) {
|
||||||
|
QString storedPath;
|
||||||
|
if (ChatSerializer::saveContentToStorage(
|
||||||
|
m_chatFilePath, file.filename, file.content.toUtf8().toBase64(), storedPath)) {
|
||||||
|
Context::ContentFile storedFile;
|
||||||
|
storedFile.filename = file.filename;
|
||||||
|
storedFile.content = storedPath;
|
||||||
|
storedAttachments.append(storedFile);
|
||||||
|
LOG_MESSAGE(QString("Stored text file %1 as %2").arg(file.filename, storedPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!textFiles.isEmpty()) {
|
||||||
|
LOG_MESSAGE(QString("Warning: Chat file path not set, cannot save %1 text file(s)")
|
||||||
|
.arg(textFiles.size()));
|
||||||
|
}
|
||||||
|
|
||||||
QList<ChatModel::ImageAttachment> imageAttachments;
|
QList<ChatModel::ImageAttachment> imageAttachments;
|
||||||
if (!imageFiles.isEmpty() && !m_chatFilePath.isEmpty()) {
|
if (!imageFiles.isEmpty() && !m_chatFilePath.isEmpty()) {
|
||||||
@ -100,7 +117,7 @@ void ClientInterface::sendMessage(
|
|||||||
|
|
||||||
QString storedPath;
|
QString storedPath;
|
||||||
QFileInfo fileInfo(imagePath);
|
QFileInfo fileInfo(imagePath);
|
||||||
if (ChatSerializer::saveImageToStorage(
|
if (ChatSerializer::saveContentToStorage(
|
||||||
m_chatFilePath, fileInfo.fileName(), base64Data, storedPath)) {
|
m_chatFilePath, fileInfo.fileName(), base64Data, storedPath)) {
|
||||||
ChatModel::ImageAttachment imageAttachment;
|
ChatModel::ImageAttachment imageAttachment;
|
||||||
imageAttachment.fileName = fileInfo.fileName();
|
imageAttachment.fileName = fileInfo.fileName();
|
||||||
@ -116,7 +133,7 @@ void ClientInterface::sendMessage(
|
|||||||
.arg(imageFiles.size()));
|
.arg(imageFiles.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_chatModel->addMessage(message, ChatModel::ChatRole::User, "", attachFiles, imageAttachments);
|
m_chatModel->addMessage(message, ChatModel::ChatRole::User, "", storedAttachments, imageAttachments);
|
||||||
|
|
||||||
auto &chatAssistantSettings = Settings::chatAssistantSettings();
|
auto &chatAssistantSettings = Settings::chatAssistantSettings();
|
||||||
|
|
||||||
@ -182,6 +199,19 @@ void ClientInterface::sendMessage(
|
|||||||
LLMCore::Message apiMessage;
|
LLMCore::Message apiMessage;
|
||||||
apiMessage.role = msg.role == ChatModel::ChatRole::User ? "user" : "assistant";
|
apiMessage.role = msg.role == ChatModel::ChatRole::User ? "user" : "assistant";
|
||||||
apiMessage.content = msg.content;
|
apiMessage.content = msg.content;
|
||||||
|
|
||||||
|
if (!msg.attachments.isEmpty() && !m_chatFilePath.isEmpty()) {
|
||||||
|
apiMessage.content += "\n\nAttached files:";
|
||||||
|
for (const auto &attachment : msg.attachments) {
|
||||||
|
QString fileContent = ChatSerializer::loadContentFromStorage(m_chatFilePath, attachment.content);
|
||||||
|
if (!fileContent.isEmpty()) {
|
||||||
|
QString decodedContent = QString::fromUtf8(QByteArray::fromBase64(fileContent.toUtf8()));
|
||||||
|
apiMessage.content += QString("\n\nFile: %1\n```\n%2\n```")
|
||||||
|
.arg(attachment.filename, decodedContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
apiMessage.isThinking = (msg.role == ChatModel::ChatRole::Thinking);
|
apiMessage.isThinking = (msg.role == ChatModel::ChatRole::Thinking);
|
||||||
apiMessage.isRedacted = msg.isRedacted;
|
apiMessage.isRedacted = msg.isRedacted;
|
||||||
apiMessage.signature = msg.signature;
|
apiMessage.signature = msg.signature;
|
||||||
@ -499,7 +529,7 @@ QVector<LLMCore::ImageAttachment> ClientInterface::loadImagesFromStorage(
|
|||||||
|
|
||||||
for (const auto &storedImage : storedImages) {
|
for (const auto &storedImage : storedImages) {
|
||||||
QString base64Data
|
QString base64Data
|
||||||
= ChatSerializer::loadImageFromStorage(m_chatFilePath, storedImage.storedPath);
|
= ChatSerializer::loadContentFromStorage(m_chatFilePath, storedImage.storedPath);
|
||||||
if (base64Data.isEmpty()) {
|
if (base64Data.isEmpty()) {
|
||||||
LOG_MESSAGE(QString("Warning: Failed to load image: %1").arg(storedImage.storedPath));
|
LOG_MESSAGE(QString("Warning: Failed to load image: %1").arg(storedImage.storedPath));
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@ -121,24 +121,11 @@ Rectangle {
|
|||||||
Repeater {
|
Repeater {
|
||||||
id: attachmentsModel
|
id: attachmentsModel
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: AttachmentComponent {
|
||||||
required property int index
|
required property int index
|
||||||
required property var modelData
|
required property var modelData
|
||||||
|
|
||||||
height: attachText.implicitHeight + 8
|
itemData: modelData
|
||||||
width: attachText.implicitWidth + 16
|
|
||||||
radius: 4
|
|
||||||
color: palette.button
|
|
||||||
border.width: 1
|
|
||||||
border.color: palette.mid
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: attachText
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: modelData
|
|
||||||
color: palette.text
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,6 +226,68 @@ Rectangle {
|
|||||||
codeFontSize: root.codeFontSize
|
codeFontSize: root.codeFontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
component AttachmentComponent : Rectangle {
|
||||||
|
required property var itemData
|
||||||
|
|
||||||
|
height: attachFileText.implicitHeight + 8
|
||||||
|
width: attachFileText.implicitWidth + 16
|
||||||
|
radius: 4
|
||||||
|
color: attachFileMouseArea.containsMouse ? Qt.lighter(palette.button, 1.1) : palette.button
|
||||||
|
border.width: 1
|
||||||
|
border.color: palette.mid
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: 100 } }
|
||||||
|
|
||||||
|
FileItem {
|
||||||
|
id: fileItem
|
||||||
|
filePath: itemData.filePath || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: attachFileText
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: (itemData.fileName || "")
|
||||||
|
color: palette.buttonText
|
||||||
|
font.pointSize: root.textFontSize - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: attachFileMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
|
onClicked: (mouse) => {
|
||||||
|
if (mouse.button === Qt.LeftButton) {
|
||||||
|
fileItem.openFileInEditor()
|
||||||
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
|
attachmentContextMenu.popup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolTip.visible: containsMouse
|
||||||
|
ToolTip.text: qsTr("Left click: Open in Qt Creator\nRight click: More options")
|
||||||
|
ToolTip.delay: 500
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: attachmentContextMenu
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: qsTr("Open in Qt Creator")
|
||||||
|
onTriggered: fileItem.openFileInEditor()
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: qsTr("Open in System Editor")
|
||||||
|
onTriggered: fileItem.openFileInExternalEditor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
component ImageComponent : Rectangle {
|
component ImageComponent : Rectangle {
|
||||||
required property var itemData
|
required property var itemData
|
||||||
|
|
||||||
@ -248,10 +297,17 @@ Rectangle {
|
|||||||
width: Math.min(imageDisplay.implicitWidth, maxImageWidth) + 16
|
width: Math.min(imageDisplay.implicitWidth, maxImageWidth) + 16
|
||||||
height: imageDisplay.implicitHeight + fileNameText.implicitHeight + 16
|
height: imageDisplay.implicitHeight + fileNameText.implicitHeight + 16
|
||||||
radius: 4
|
radius: 4
|
||||||
color: palette.base
|
color: imageMouseArea.containsMouse ? Qt.lighter(palette.base, 1.05) : palette.base
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: palette.mid
|
border.color: palette.mid
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: 100 } }
|
||||||
|
|
||||||
|
FileItem {
|
||||||
|
id: imageFileItem
|
||||||
|
filePath: itemData.imageUrl ? itemData.imageUrl.toString().replace("file://", "") : ""
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 8
|
anchors.margins: 8
|
||||||
@ -259,6 +315,7 @@ Rectangle {
|
|||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: imageDisplay
|
id: imageDisplay
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.maximumWidth: parent.parent.maxImageWidth
|
Layout.maximumWidth: parent.parent.maxImageWidth
|
||||||
Layout.maximumHeight: parent.parent.maxImageHeight
|
Layout.maximumHeight: parent.parent.maxImageHeight
|
||||||
@ -289,6 +346,7 @@ Rectangle {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: fileNameText
|
id: fileNameText
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: itemData.fileName || ""
|
text: itemData.fileName || ""
|
||||||
color: palette.text
|
color: palette.text
|
||||||
@ -297,5 +355,40 @@ Rectangle {
|
|||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: imageMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
|
onClicked: (mouse) => {
|
||||||
|
if (mouse.button === Qt.LeftButton) {
|
||||||
|
imageFileItem.openFileInEditor()
|
||||||
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
|
imageContextMenu.popup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolTip.visible: containsMouse
|
||||||
|
ToolTip.text: qsTr("Left click: Open in System\nRight click: More options")
|
||||||
|
ToolTip.delay: 500
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: imageContextMenu
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: qsTr("Open in Qt Creator")
|
||||||
|
onTriggered: imageFileItem.openFileInEditor()
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: qsTr("Open in System Viewer")
|
||||||
|
onTriggered: imageFileItem.openFileInExternalEditor()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,11 +43,17 @@ ContextManager::ContextManager(QObject *parent)
|
|||||||
QString ContextManager::readFile(const QString &filePath) const
|
QString ContextManager::readFile(const QString &filePath) const
|
||||||
{
|
{
|
||||||
QFile file(filePath);
|
QFile file(filePath);
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
LOG_MESSAGE(QString("Failed to open file for reading: %1 - %2")
|
||||||
|
.arg(filePath, file.errorString()));
|
||||||
return QString();
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
QTextStream in(&file);
|
QTextStream in(&file);
|
||||||
return in.readAll();
|
QString content = in.readAll();
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<ContentFile> ContextManager::getContentFiles(const QStringList &filePaths) const
|
QList<ContentFile> ContextManager::getContentFiles(const QStringList &filePaths) const
|
||||||
|
|||||||
@ -33,6 +33,31 @@ If issues persist, you can reset settings to their default values:
|
|||||||
- API keys are preserved during reset
|
- API keys are preserved during reset
|
||||||
- You will need to re-select your model after reset
|
- You will need to re-select your model after reset
|
||||||
|
|
||||||
|
## Chat History Migration
|
||||||
|
|
||||||
|
### Images not showing in old chats (version 0.5.x → 0.6.x)
|
||||||
|
|
||||||
|
If you have chat histories from QodeAssist version 0.5.x or earlier, images may not display correctly due to a storage structure change.
|
||||||
|
|
||||||
|
**Solution:** Rename the content folder for each affected chat:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Navigate to your chat history folder
|
||||||
|
cd ~/path/to/chat_history
|
||||||
|
|
||||||
|
# For each chat file, rename its folder
|
||||||
|
mv chat_name_images chat_name_content
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
mv my_conversation_2024-11-28_images my_conversation_2024-11-28_content
|
||||||
|
```
|
||||||
|
|
||||||
|
**What changed:**
|
||||||
|
- Old format (v0.1): Stored files in `chat_name_images/`
|
||||||
|
- New format (v0.2): Stores all content in `chat_name_content/` (both images and text files)
|
||||||
|
|
||||||
## Common Issues
|
## Common Issues
|
||||||
|
|
||||||
### Plugin doesn't appear after installation
|
### Plugin doesn't appear after installation
|
||||||
|
|||||||
@ -155,6 +155,9 @@ void QuickRefactorDialog::setupUi()
|
|||||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
mainLayout->addWidget(buttonBox);
|
mainLayout->addWidget(buttonBox);
|
||||||
|
|
||||||
|
setTabOrder(m_commandsComboBox, m_textEdit);
|
||||||
|
setTabOrder(m_textEdit, buttonBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QuickRefactorDialog::createActionButtons()
|
void QuickRefactorDialog::createActionButtons()
|
||||||
|
|||||||
Reference in New Issue
Block a user