feat: Add temp file storage for chat (#279)

* fix: Add signature to chat history
* feat: Add file storage for chat
This commit is contained in:
Petr Mironychev
2025-11-28 13:59:43 +01:00
committed by GitHub
parent 595895840a
commit 22377c8f6a
8 changed files with 426 additions and 76 deletions

View File

@ -63,6 +63,7 @@ qt_add_qml_module(QodeAssistChatView
ChatView.hpp ChatView.cpp ChatView.hpp ChatView.cpp
ChatData.hpp ChatData.hpp
FileItem.hpp FileItem.cpp FileItem.hpp FileItem.cpp
ChatFileManager.hpp ChatFileManager.cpp
) )
target_link_libraries(QodeAssistChatView target_link_libraries(QodeAssistChatView

View File

@ -0,0 +1,206 @@
/*
* Copyright (C) 2024-2025 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ChatFileManager.hpp"
#include "Logger.hpp"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QStandardPaths>
#include <QUuid>
#include <QDateTime>
#include <QRegularExpression>
#include <coreplugin/icore.h>
namespace QodeAssist::Chat {
ChatFileManager::ChatFileManager(QObject *parent)
: QObject(parent)
, m_intermediateStorageDir(getIntermediateStorageDir())
{}
ChatFileManager::~ChatFileManager() = default;
QStringList ChatFileManager::processDroppedFiles(const QStringList &filePaths)
{
QStringList processedPaths;
processedPaths.reserve(filePaths.size());
for (const QString &filePath : filePaths) {
if (!isFileAccessible(filePath)) {
const QString error = tr("File is not accessible: %1").arg(filePath);
LOG_MESSAGE(error);
emit fileOperationFailed(error);
continue;
}
QString copiedPath = copyToIntermediateStorage(filePath);
if (!copiedPath.isEmpty()) {
processedPaths.append(copiedPath);
emit fileCopiedToStorage(filePath, copiedPath);
LOG_MESSAGE(QString("File copied to storage: %1 -> %2").arg(filePath, copiedPath));
} else {
const QString error = tr("Failed to copy file: %1").arg(filePath);
LOG_MESSAGE(error);
emit fileOperationFailed(error);
}
}
return processedPaths;
}
void ChatFileManager::setChatFilePath(const QString &chatFilePath)
{
m_chatFilePath = chatFilePath;
}
QString ChatFileManager::chatFilePath() const
{
return m_chatFilePath;
}
void ChatFileManager::clearIntermediateStorage()
{
QDir dir(m_intermediateStorageDir);
if (!dir.exists()) {
return;
}
const QFileInfoList files = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
for (const QFileInfo &fileInfo : files) {
QFile file(fileInfo.absoluteFilePath());
file.setPermissions(QFile::WriteUser | QFile::ReadUser);
if (file.remove()) {
LOG_MESSAGE(QString("Removed intermediate file: %1").arg(fileInfo.fileName()));
} else {
LOG_MESSAGE(QString("Failed to remove intermediate file: %1")
.arg(fileInfo.fileName()));
}
}
}
bool ChatFileManager::isFileAccessible(const QString &filePath)
{
QFileInfo fileInfo(filePath);
return fileInfo.exists() && fileInfo.isFile() && fileInfo.isReadable();
}
void ChatFileManager::cleanupGlobalIntermediateStorage()
{
const QString basePath = Core::ICore::userResourcePath().toFSPathString();
const QString intermediatePath = QDir(basePath).filePath("qodeassist/chat_temp_files");
QDir dir(intermediatePath);
if (!dir.exists()) {
return;
}
const QFileInfoList files = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
int removedCount = 0;
int failedCount = 0;
for (const QFileInfo &fileInfo : files) {
QFile file(fileInfo.absoluteFilePath());
file.setPermissions(QFile::WriteUser | QFile::ReadUser);
if (file.remove()) {
removedCount++;
} else {
failedCount++;
}
}
if (removedCount > 0 || failedCount > 0) {
LOG_MESSAGE(QString("ChatFileManager global cleanup: removed=%1, failed=%2")
.arg(removedCount)
.arg(failedCount));
}
}
QString ChatFileManager::copyToIntermediateStorage(const QString &filePath)
{
QFileInfo fileInfo(filePath);
if (!fileInfo.exists() || !fileInfo.isFile()) {
LOG_MESSAGE(QString("Source file does not exist or is not a file: %1").arg(filePath));
return QString();
}
if (fileInfo.size() == 0) {
LOG_MESSAGE(QString("Source file is empty: %1").arg(filePath));
}
const QString newFileName = generateIntermediateFileName(filePath);
const QString destinationPath = QDir(m_intermediateStorageDir).filePath(newFileName);
if (QFileInfo::exists(destinationPath)) {
QFile::remove(destinationPath);
}
if (!QFile::copy(filePath, destinationPath)) {
LOG_MESSAGE(QString("Failed to copy file: %1 -> %2").arg(filePath, destinationPath));
return QString();
}
QFile copiedFile(destinationPath);
if (!copiedFile.exists()) {
LOG_MESSAGE(QString("Copied file does not exist after copy: %1").arg(destinationPath));
return QString();
}
copiedFile.setPermissions(QFile::ReadUser | QFile::WriteUser);
return destinationPath;
}
QString ChatFileManager::getIntermediateStorageDir()
{
const QString basePath = Core::ICore::userResourcePath().toFSPathString();
const QString intermediatePath = QDir(basePath).filePath("qodeassist/chat_temp_files");
QDir dir;
if (!dir.exists(intermediatePath) && !dir.mkpath(intermediatePath)) {
LOG_MESSAGE(QString("Failed to create intermediate storage directory: %1")
.arg(intermediatePath));
}
return intermediatePath;
}
QString ChatFileManager::generateIntermediateFileName(const QString &originalPath)
{
const QFileInfo fileInfo(originalPath);
const QString extension = fileInfo.suffix();
QString baseName = fileInfo.completeBaseName().left(30);
static const QRegularExpression specialChars("[^a-zA-Z0-9_-]");
baseName.replace(specialChars, "_");
if (baseName.isEmpty()) {
baseName = "file";
}
const QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss");
const QString uuid = QUuid::createUuid().toString(QUuid::WithoutBraces).left(8);
return QString("%1_%2_%3.%4").arg(baseName, timestamp, uuid, extension);
}
} // namespace QodeAssist::Chat

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2024-2025 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QObject>
#include <QString>
#include <QStringList>
#include <QMap>
namespace QodeAssist::Chat {
class ChatFileManager : public QObject
{
Q_OBJECT
public:
explicit ChatFileManager(QObject *parent = nullptr);
~ChatFileManager();
QStringList processDroppedFiles(const QStringList &filePaths);
void setChatFilePath(const QString &chatFilePath);
QString chatFilePath() const;
void clearIntermediateStorage();
static bool isFileAccessible(const QString &filePath);
static void cleanupGlobalIntermediateStorage();
signals:
void fileOperationFailed(const QString &error);
void fileCopiedToStorage(const QString &originalPath, const QString &newPath);
private:
QString copyToIntermediateStorage(const QString &filePath);
QString getIntermediateStorageDir();
QString generateIntermediateFileName(const QString &originalPath);
QString m_chatFilePath;
QString m_intermediateStorageDir;
};
} // namespace QodeAssist::Chat

View File

@ -22,6 +22,7 @@
#include <QClipboard> #include <QClipboard>
#include <QDesktopServices> #include <QDesktopServices>
#include <QFileDialog> #include <QFileDialog>
#include <QFileInfo>
#include <QMessageBox> #include <QMessageBox>
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
@ -53,6 +54,7 @@ ChatRootView::ChatRootView(QQuickItem *parent)
, m_chatModel(new ChatModel(this)) , m_chatModel(new ChatModel(this))
, m_promptProvider(LLMCore::PromptTemplateManager::instance()) , m_promptProvider(LLMCore::PromptTemplateManager::instance())
, m_clientInterface(new ClientInterface(m_chatModel, &m_promptProvider, this)) , m_clientInterface(new ClientInterface(m_chatModel, &m_promptProvider, this))
, m_fileManager(new ChatFileManager(this))
, m_isRequestInProgress(false) , m_isRequestInProgress(false)
{ {
m_isSyncOpenFiles = Settings::chatAssistantSettings().linkOpenFiles(); m_isSyncOpenFiles = Settings::chatAssistantSettings().linkOpenFiles();
@ -230,6 +232,11 @@ ChatRootView::ChatRootView(QQuickItem *parent)
&Utils::BaseAspect::changed, &Utils::BaseAspect::changed,
this, this,
&ChatRootView::isThinkingSupportChanged); &ChatRootView::isThinkingSupportChanged);
connect(m_fileManager, &ChatFileManager::fileOperationFailed, this, [this](const QString &error) {
m_lastErrorMessage = error;
emit lastErrorMessageChanged();
});
} }
ChatModel *ChatRootView::chatModel() const ChatModel *ChatRootView::chatModel() const
@ -265,6 +272,8 @@ void ChatRootView::sendMessage(const QString &message)
m_clientInterface m_clientInterface
->sendMessage(message, m_attachmentFiles, m_linkedFiles, useTools(), useThinking()); ->sendMessage(message, m_attachmentFiles, m_linkedFiles, useTools(), useThinking());
m_fileManager->clearIntermediateStorage();
clearAttachmentFiles(); clearAttachmentFiles();
setRequestProgressStatus(true); setRequestProgressStatus(true);
} }
@ -282,18 +291,23 @@ void ChatRootView::cancelRequest()
void ChatRootView::clearAttachmentFiles() void ChatRootView::clearAttachmentFiles()
{ {
if (!m_attachmentFiles.isEmpty()) { if (m_attachmentFiles.isEmpty()) {
m_attachmentFiles.clear(); return;
emit attachmentFilesChanged();
} }
m_attachmentFiles.clear();
emit attachmentFilesChanged();
m_fileManager->clearIntermediateStorage();
} }
void ChatRootView::clearLinkedFiles() void ChatRootView::clearLinkedFiles()
{ {
if (!m_linkedFiles.isEmpty()) { if (m_linkedFiles.isEmpty()) {
m_linkedFiles.clear(); return;
emit linkedFilesChanged();
} }
m_linkedFiles.clear();
emit linkedFilesChanged();
} }
QString ChatRootView::getChatsHistoryDir() const QString ChatRootView::getChatsHistoryDir() const
@ -304,8 +318,8 @@ QString ChatRootView::getChatsHistoryDir() const
Settings::ProjectSettings projectSettings(project); Settings::ProjectSettings projectSettings(project);
path = projectSettings.chatHistoryPath().toFSPathString(); path = projectSettings.chatHistoryPath().toFSPathString();
} else { } else {
path = QString("%1/qodeassist/chat_history") QDir baseDir(Core::ICore::userResourcePath().toFSPathString());
.arg(Core::ICore::userResourcePath().toFSPathString()); path = baseDir.filePath("qodeassist/chat_history");
} }
QDir dir(path); QDir dir(path);
@ -342,6 +356,12 @@ void ChatRootView::loadHistory(const QString &filePath)
setRecentFilePath(filePath); setRecentFilePath(filePath);
} }
m_fileManager->clearIntermediateStorage();
m_attachmentFiles.clear();
m_linkedFiles.clear();
emit attachmentFilesChanged();
emit linkedFilesChanged();
m_currentMessageRequestId.clear(); m_currentMessageRequestId.clear();
updateInputTokensCount(); updateInputTokensCount();
updateCurrentMessageEditsStats(); updateCurrentMessageEditsStats();
@ -499,8 +519,10 @@ void ChatRootView::addFilesToAttachList(const QStringList &filePaths)
return; return;
} }
const QStringList processedPaths = m_fileManager->processDroppedFiles(filePaths);
bool filesAdded = false; bool filesAdded = false;
for (const QString &filePath : filePaths) { for (const QString &filePath : processedPaths) {
if (!m_attachmentFiles.contains(filePath)) { if (!m_attachmentFiles.contains(filePath)) {
m_attachmentFiles.append(filePath); m_attachmentFiles.append(filePath);
filesAdded = true; filesAdded = true;
@ -514,10 +536,15 @@ void ChatRootView::addFilesToAttachList(const QStringList &filePaths)
void ChatRootView::removeFileFromAttachList(int index) void ChatRootView::removeFileFromAttachList(int index)
{ {
if (index >= 0 && index < m_attachmentFiles.size()) { if (index < 0 || index >= m_attachmentFiles.size()) {
m_attachmentFiles.removeAt(index); return;
emit attachmentFilesChanged();
} }
const QString removedFile = m_attachmentFiles.at(index);
m_attachmentFiles.removeAt(index);
emit attachmentFilesChanged();
LOG_MESSAGE(QString("Removed attachment file: %1").arg(removedFile));
} }
void ChatRootView::showLinkFilesDialog() void ChatRootView::showLinkFilesDialog()
@ -557,7 +584,6 @@ void ChatRootView::addFilesToLinkList(const QStringList &filePaths)
if (!imageFiles.isEmpty()) { if (!imageFiles.isEmpty()) {
addFilesToAttachList(imageFiles); addFilesToAttachList(imageFiles);
m_lastInfoMessage m_lastInfoMessage
= tr("Images automatically moved to Attach zone (%n file(s))", "", imageFiles.size()); = tr("Images automatically moved to Attach zone (%n file(s))", "", imageFiles.size());
emit lastInfoMessageChanged(); emit lastInfoMessageChanged();
@ -570,10 +596,15 @@ void ChatRootView::addFilesToLinkList(const QStringList &filePaths)
void ChatRootView::removeFileFromLinkList(int index) void ChatRootView::removeFileFromLinkList(int index)
{ {
if (index >= 0 && index < m_linkedFiles.size()) { if (index < 0 || index >= m_linkedFiles.size()) {
m_linkedFiles.removeAt(index); return;
emit linkedFilesChanged();
} }
const QString removedFile = m_linkedFiles.at(index);
m_linkedFiles.removeAt(index);
emit linkedFilesChanged();
LOG_MESSAGE(QString("Removed linked file: %1").arg(removedFile));
} }
void ChatRootView::showAddImageDialog() void ChatRootView::showAddImageDialog()
@ -587,19 +618,7 @@ void ChatRootView::showAddImageDialog()
} }
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
QStringList newFilePaths = dialog.selectedFiles(); addFilesToAttachList(dialog.selectedFiles());
if (!newFilePaths.isEmpty()) {
bool filesAdded = false;
for (const QString &filePath : std::as_const(newFilePaths)) {
if (!m_attachmentFiles.contains(filePath)) {
m_attachmentFiles.append(filePath);
filesAdded = true;
}
}
if (filesAdded) {
emit attachmentFilesChanged();
}
}
} }
} }
@ -645,8 +664,8 @@ void ChatRootView::openChatHistoryFolder()
Settings::ProjectSettings projectSettings(project); Settings::ProjectSettings projectSettings(project);
path = projectSettings.chatHistoryPath().toFSPathString(); path = projectSettings.chatHistoryPath().toFSPathString();
} else { } else {
path = QString("%1/qodeassist/chat_history") QDir baseDir(Core::ICore::userResourcePath().toFSPathString());
.arg(Core::ICore::userResourcePath().toFSPathString()); path = baseDir.filePath("qodeassist/chat_history");
} }
QDir dir(path); QDir dir(path);
@ -666,7 +685,7 @@ void ChatRootView::openRulesFolder()
} }
QString projectPath = project->projectDirectory().toFSPathString(); QString projectPath = project->projectDirectory().toFSPathString();
QString rulesPath = projectPath + "/.qodeassist/rules"; QString rulesPath = QDir(projectPath).filePath(".qodeassist/rules");
QDir dir(rulesPath); QDir dir(rulesPath);
if (!dir.exists()) { if (!dir.exists()) {
@ -762,6 +781,7 @@ void ChatRootView::setRecentFilePath(const QString &filePath)
if (m_recentFilePath != filePath) { if (m_recentFilePath != filePath) {
m_recentFilePath = filePath; m_recentFilePath = filePath;
m_clientInterface->setChatFilePath(filePath); m_clientInterface->setChatFilePath(filePath);
m_fileManager->setChatFilePath(filePath);
emit chatFileNameChanged(); emit chatFileNameChanged();
} }
} }

View File

@ -23,6 +23,7 @@
#include "ChatModel.hpp" #include "ChatModel.hpp"
#include "ClientInterface.hpp" #include "ClientInterface.hpp"
#include "ChatFileManager.hpp"
#include "llmcore/PromptProviderChat.hpp" #include "llmcore/PromptProviderChat.hpp"
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
@ -199,6 +200,7 @@ private:
ChatModel *m_chatModel; ChatModel *m_chatModel;
LLMCore::PromptProviderChat m_promptProvider; LLMCore::PromptProviderChat m_promptProvider;
ClientInterface *m_clientInterface; ClientInterface *m_clientInterface;
ChatFileManager *m_fileManager;
QString m_currentTemplate; QString m_currentTemplate;
QString m_recentFilePath; QString m_recentFilePath;
QStringList m_attachmentFiles; QStringList m_attachmentFiles;

View File

@ -60,6 +60,7 @@ ChatRootView {
SplitDropZone { SplitDropZone {
anchors.fill: parent anchors.fill: parent
z: 99
onFilesDroppedToAttach: (urlStrings) => { onFilesDroppedToAttach: (urlStrings) => {
var localPaths = root.convertUrlsToLocalPaths(urlStrings) var localPaths = root.convertUrlsToLocalPaths(urlStrings)

View File

@ -23,10 +23,12 @@ import QtQuick.Controls
Item { Item {
id: root id: root
signal filesDroppedToAttach(var urlStrings) // Array of URL strings (file://...) signal filesDroppedToAttach(var urlStrings)
signal filesDroppedToLink(var urlStrings) // Array of URL strings (file://...) signal filesDroppedToLink(var urlStrings)
property string activeZone: "" property string activeZone: ""
property int filesCount: 0
property bool isDragActive: false
Item { Item {
id: splitDropOverlay id: splitDropOverlay
@ -34,12 +36,39 @@ Item {
anchors.fill: parent anchors.fill: parent
visible: false visible: false
z: 999 z: 999
opacity: 0
Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Qt.rgba(palette.shadow.r, palette.shadow.g, palette.shadow.b, 0.6) color: Qt.rgba(palette.shadow.r, palette.shadow.g, palette.shadow.b, 0.6)
} }
Rectangle {
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
topMargin: 30
}
width: fileCountText.width + 40
height: 50
color: Qt.rgba(palette.highlight.r, palette.highlight.g, palette.highlight.b, 0.9)
radius: 25
visible: root.filesCount > 0
Text {
id: fileCountText
anchors.centerIn: parent
text: qsTr("%n file(s) to drop", "", root.filesCount)
font.pixelSize: 16
font.bold: true
color: palette.highlightedText
}
}
Rectangle { Rectangle {
id: leftZone id: leftZone
@ -76,19 +105,20 @@ Item {
color: root.activeZone === "left" ? palette.highlightedText : palette.text color: root.activeZone === "left" ? palette.highlightedText : palette.text
opacity: 0.8 opacity: 0.8
} }
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("(for one-time use)")
font.pixelSize: 12
font.italic: true
color: root.activeZone === "left" ? palette.highlightedText : palette.text
opacity: 0.6
}
} }
Behavior on color { Behavior on color { ColorAnimation { duration: 150 } }
ColorAnimation { duration: 150 } Behavior on border.width { NumberAnimation { duration: 150 } }
} Behavior on border.color { ColorAnimation { duration: 150 } }
Behavior on border.width {
NumberAnimation { duration: 150 }
}
Behavior on border.color {
ColorAnimation { duration: 150 }
}
} }
Rectangle { Rectangle {
@ -127,19 +157,20 @@ Item {
color: root.activeZone === "right" ? palette.highlightedText : palette.text color: root.activeZone === "right" ? palette.highlightedText : palette.text
opacity: 0.8 opacity: 0.8
} }
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("(added to context)")
font.pixelSize: 12
font.italic: true
color: root.activeZone === "right" ? palette.highlightedText : palette.text
opacity: 0.6
}
} }
Behavior on color { Behavior on color { ColorAnimation { duration: 150 } }
ColorAnimation { duration: 150 } Behavior on border.width { NumberAnimation { duration: 150 } }
} Behavior on border.color { ColorAnimation { duration: 150 } }
Behavior on border.width {
NumberAnimation { duration: 150 }
}
Behavior on border.color {
ColorAnimation { duration: 150 }
}
} }
Rectangle { Rectangle {
@ -193,42 +224,67 @@ Item {
onEntered: (drag) => { onEntered: (drag) => {
if (drag.hasUrls) { if (drag.hasUrls) {
root.isDragActive = true
root.filesCount = drag.urls.length
splitDropOverlay.visible = true splitDropOverlay.visible = true
splitDropOverlay.opacity = 1
root.activeZone = "" root.activeZone = ""
} }
} }
onExited: { onExited: {
splitDropOverlay.visible = false root.isDragActive = false
root.activeZone = "" root.filesCount = 0
splitDropOverlay.opacity = 0
Qt.callLater(function() {
if (!root.isDragActive) {
splitDropOverlay.visible = false
root.activeZone = ""
}
})
} }
onPositionChanged: (drag) => { onPositionChanged: (drag) => {
if (drag.x < globalDropArea.width / 2) { if (drag.hasUrls) {
root.activeZone = "left" root.activeZone = drag.x < globalDropArea.width / 2 ? "left" : "right"
} else {
root.activeZone = "right"
} }
} }
onDropped: (drop) => { onDropped: (drop) => {
var targetZone = root.activeZone const targetZone = root.activeZone
splitDropOverlay.visible = false root.isDragActive = false
root.activeZone = "" root.filesCount = 0
splitDropOverlay.opacity = 0
if (drop.hasUrls && drop.urls.length > 0) { Qt.callLater(function() {
// Convert URLs to array of strings for C++ processing splitDropOverlay.visible = false
var urlStrings = [] root.activeZone = ""
for (var i = 0; i < drop.urls.length; i++) { })
urlStrings.push(drop.urls[i].toString())
} if (!drop.hasUrls || drop.urls.length === 0) {
return
if (targetZone === "right") { }
root.filesDroppedToLink(urlStrings)
} else { var urlStrings = []
root.filesDroppedToAttach(urlStrings) for (var i = 0; i < drop.urls.length; i++) {
var urlString = drop.urls[i].toString()
if (urlString.startsWith("file://") || urlString.indexOf("://") === -1) {
urlStrings.push(urlString)
} }
} }
if (urlStrings.length === 0) {
return
}
drop.accept(Qt.CopyAction)
if (targetZone === "right") {
root.filesDroppedToLink(urlStrings)
} else {
root.filesDroppedToAttach(urlStrings)
}
} }
} }
} }

View File

@ -62,6 +62,7 @@
#include "widgets/CustomInstructionsManager.hpp" #include "widgets/CustomInstructionsManager.hpp"
#include "widgets/QuickRefactorDialog.hpp" #include "widgets/QuickRefactorDialog.hpp"
#include <ChatView/ChatView.hpp> #include <ChatView/ChatView.hpp>
#include <ChatView/ChatFileManager.hpp>
#include <coreplugin/actionmanager/actioncontainer.h> #include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/actionmanager/actionmanager.h>
#include <texteditor/textdocument.h> #include <texteditor/textdocument.h>
@ -87,6 +88,8 @@ public:
~QodeAssistPlugin() final ~QodeAssistPlugin() final
{ {
Chat::ChatFileManager::cleanupGlobalIntermediateStorage();
delete m_qodeAssistClient; delete m_qodeAssistClient;
if (m_chatOutputPane) { if (m_chatOutputPane) {
delete m_chatOutputPane; delete m_chatOutputPane;
@ -249,6 +252,8 @@ public:
editorContextMenu->addAction(closeChatViewAction.command(), editorContextMenu->addAction(closeChatViewAction.command(),
Core::Constants::G_DEFAULT_THREE); Core::Constants::G_DEFAULT_THREE);
} }
Chat::ChatFileManager::cleanupGlobalIntermediateStorage();
} }
void extensionsInitialized() final {} void extensionsInitialized() final {}