mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-06-30 10:09:19 -04:00
fix: Merging tool result
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
#include <QSet>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include <LLMQore/ContentBlocks.hpp>
|
#include <LLMQore/ContentBlocks.hpp>
|
||||||
@@ -114,6 +115,7 @@ void ChatModel::setHistory(ConversationHistory *history)
|
|||||||
}
|
}
|
||||||
|
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
|
m_usageByMessageId.clear();
|
||||||
rebuildAll();
|
rebuildAll();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
emit sessionUsageChanged();
|
emit sessionUsageChanged();
|
||||||
@@ -137,7 +139,7 @@ QVariant ChatModel::data(const QModelIndex &index, int role) const
|
|||||||
return QVariant::fromValue(row.kind);
|
return QVariant::fromValue(row.kind);
|
||||||
case Roles::Content:
|
case Roles::Content:
|
||||||
if (row.kind == ChatRole::FileEdit)
|
if (row.kind == ChatRole::FileEdit)
|
||||||
return overlayFileEditStatus(row.content, row.editId);
|
return row.fileEditDisplay;
|
||||||
return row.content;
|
return row.content;
|
||||||
case Roles::Attachments:
|
case Roles::Attachments:
|
||||||
return buildAttachmentList(row.attachments);
|
return buildAttachmentList(row.attachments);
|
||||||
@@ -364,6 +366,7 @@ void ChatModel::appendRowsForMessage(
|
|||||||
editRow.messageId = id;
|
editRow.messageId = id;
|
||||||
editRow.content = result;
|
editRow.content = result;
|
||||||
editRow.editId = parseEditId(result);
|
editRow.editId = parseEditId(result);
|
||||||
|
editRow.fileEditDisplay = overlayFileEditStatus(result, editRow.editId);
|
||||||
out.append(std::move(editRow));
|
out.append(std::move(editRow));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -376,20 +379,54 @@ void ChatModel::appendRowsForMessage(
|
|||||||
void ChatModel::rebuildAll()
|
void ChatModel::rebuildAll()
|
||||||
{
|
{
|
||||||
m_rows.clear();
|
m_rows.clear();
|
||||||
|
m_toolResults.clear();
|
||||||
if (!m_history)
|
if (!m_history)
|
||||||
return;
|
return;
|
||||||
const QHash<QString, QString> toolResults = buildToolResultMap();
|
m_toolResults = buildToolResultMap();
|
||||||
for (int mi = 0; mi < m_history->size(); ++mi)
|
for (int mi = 0; mi < m_history->size(); ++mi)
|
||||||
appendRowsForMessage(mi, toolResults, m_rows);
|
appendRowsForMessage(mi, m_toolResults, m_rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatModel::mergeToolResultsFromMessage(int messageIndex)
|
||||||
|
{
|
||||||
|
if (!m_history || messageIndex < 0 || messageIndex >= m_history->size())
|
||||||
|
return;
|
||||||
|
const Message &m = m_history->messages()[static_cast<size_t>(messageIndex)];
|
||||||
|
for (const auto &block : m.blocks()) {
|
||||||
|
if (auto *tr = dynamic_cast<LLMQore::ToolResultContent *>(block.get()))
|
||||||
|
m_toolResults.insert(tr->toolUseId(), tr->result());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatModel::pruneUsageToHistory()
|
||||||
|
{
|
||||||
|
if (!m_history) {
|
||||||
|
m_usageByMessageId.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QSet<QString> liveIds;
|
||||||
|
for (const auto &m : m_history->messages())
|
||||||
|
liveIds.insert(m.id());
|
||||||
|
for (auto it = m_usageByMessageId.begin(); it != m_usageByMessageId.end();) {
|
||||||
|
if (!liveIds.contains(it.key()))
|
||||||
|
it = m_usageByMessageId.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int ChatModel::firstRowForMessage(int messageIndex) const
|
int ChatModel::firstRowForMessage(int messageIndex) const
|
||||||
{
|
{
|
||||||
for (int i = 0; i < m_rows.size(); ++i) {
|
int lo = 0;
|
||||||
if (m_rows[i].messageIndex >= messageIndex)
|
int hi = m_rows.size();
|
||||||
return i;
|
while (lo < hi) {
|
||||||
|
const int mid = lo + (hi - lo) / 2;
|
||||||
|
if (m_rows[mid].messageIndex < messageIndex)
|
||||||
|
lo = mid + 1;
|
||||||
|
else
|
||||||
|
hi = mid;
|
||||||
}
|
}
|
||||||
return m_rows.size();
|
return lo;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ChatModel::startMessageIndexFor(int messageIndex) const
|
int ChatModel::startMessageIndexFor(int messageIndex) const
|
||||||
@@ -414,11 +451,10 @@ void ChatModel::reprojectTail(int startMessageIndex)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
const int oldStart = firstRowForMessage(startMessageIndex);
|
const int oldStart = firstRowForMessage(startMessageIndex);
|
||||||
const QHash<QString, QString> toolResults = buildToolResultMap();
|
|
||||||
|
|
||||||
QVector<Row> newTail;
|
QVector<Row> newTail;
|
||||||
for (int mi = startMessageIndex; mi < m_history->size(); ++mi)
|
for (int mi = startMessageIndex; mi < m_history->size(); ++mi)
|
||||||
appendRowsForMessage(mi, toolResults, newTail);
|
appendRowsForMessage(mi, m_toolResults, newTail);
|
||||||
|
|
||||||
const int oldCount = m_rows.size() - oldStart;
|
const int oldCount = m_rows.size() - oldStart;
|
||||||
const int newCount = newTail.size();
|
const int newCount = newTail.size();
|
||||||
@@ -443,11 +479,13 @@ void ChatModel::reprojectTail(int startMessageIndex)
|
|||||||
|
|
||||||
void ChatModel::onHistoryMessageAdded(int index)
|
void ChatModel::onHistoryMessageAdded(int index)
|
||||||
{
|
{
|
||||||
|
mergeToolResultsFromMessage(index);
|
||||||
reprojectTail(startMessageIndexFor(index));
|
reprojectTail(startMessageIndexFor(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatModel::onHistoryMessageUpdated(int index)
|
void ChatModel::onHistoryMessageUpdated(int index)
|
||||||
{
|
{
|
||||||
|
mergeToolResultsFromMessage(index);
|
||||||
reprojectTail(startMessageIndexFor(index));
|
reprojectTail(startMessageIndexFor(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,6 +493,7 @@ void ChatModel::onHistoryCleared()
|
|||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_rows.clear();
|
m_rows.clear();
|
||||||
|
m_toolResults.clear();
|
||||||
m_usageByMessageId.clear();
|
m_usageByMessageId.clear();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
emit modelReseted();
|
emit modelReseted();
|
||||||
@@ -465,6 +504,7 @@ void ChatModel::onHistoryReset()
|
|||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
rebuildAll();
|
rebuildAll();
|
||||||
|
pruneUsageToHistory();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
emit sessionUsageChanged();
|
emit sessionUsageChanged();
|
||||||
}
|
}
|
||||||
@@ -472,8 +512,10 @@ void ChatModel::onHistoryReset()
|
|||||||
void ChatModel::onFileEditStatusChanged(const QString &editId)
|
void ChatModel::onFileEditStatusChanged(const QString &editId)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < m_rows.size(); ++i) {
|
for (int i = 0; i < m_rows.size(); ++i) {
|
||||||
if (m_rows[i].kind == ChatRole::FileEdit && m_rows[i].editId == editId)
|
if (m_rows[i].kind == ChatRole::FileEdit && m_rows[i].editId == editId) {
|
||||||
|
m_rows[i].fileEditDisplay = overlayFileEditStatus(m_rows[i].content, editId);
|
||||||
emit dataChanged(index(i), index(i), {Roles::Content});
|
emit dataChanged(index(i), index(i), {Roles::Content});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ class ChatModel : public QAbstractListModel
|
|||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum ChatRole { System, User, Assistant, Tool, FileEdit, Thinking };
|
enum ChatRole : int { System, User, Assistant, Tool, FileEdit, Thinking };
|
||||||
Q_ENUM(ChatRole)
|
Q_ENUM(ChatRole)
|
||||||
|
|
||||||
enum Roles {
|
enum Roles : int {
|
||||||
RoleType = Qt::UserRole,
|
RoleType = Qt::UserRole,
|
||||||
Content,
|
Content,
|
||||||
Attachments,
|
Attachments,
|
||||||
@@ -105,6 +105,7 @@ private:
|
|||||||
QString content;
|
QString content;
|
||||||
bool isRedacted = false;
|
bool isRedacted = false;
|
||||||
QString editId;
|
QString editId;
|
||||||
|
QString fileEditDisplay;
|
||||||
QVector<AttachmentRef> attachments;
|
QVector<AttachmentRef> attachments;
|
||||||
QVector<ImageRef> images;
|
QVector<ImageRef> images;
|
||||||
};
|
};
|
||||||
@@ -121,6 +122,8 @@ private:
|
|||||||
int startMessageIndexFor(int messageIndex) const;
|
int startMessageIndexFor(int messageIndex) const;
|
||||||
int firstRowForMessage(int messageIndex) const;
|
int firstRowForMessage(int messageIndex) const;
|
||||||
QHash<QString, QString> buildToolResultMap() const;
|
QHash<QString, QString> buildToolResultMap() const;
|
||||||
|
void mergeToolResultsFromMessage(int messageIndex);
|
||||||
|
void pruneUsageToHistory();
|
||||||
void appendRowsForMessage(
|
void appendRowsForMessage(
|
||||||
int messageIndex, const QHash<QString, QString> &toolResults, QVector<Row> &out) const;
|
int messageIndex, const QHash<QString, QString> &toolResults, QVector<Row> &out) const;
|
||||||
QString overlayFileEditStatus(const QString &content, const QString &editId) const;
|
QString overlayFileEditStatus(const QString &content, const QString &editId) const;
|
||||||
@@ -129,6 +132,7 @@ private:
|
|||||||
|
|
||||||
QPointer<ConversationHistory> m_history;
|
QPointer<ConversationHistory> m_history;
|
||||||
QVector<Row> m_rows;
|
QVector<Row> m_rows;
|
||||||
|
QHash<QString, QString> m_toolResults;
|
||||||
QHash<QString, Usage> m_usageByMessageId;
|
QHash<QString, Usage> m_usageByMessageId;
|
||||||
QString m_chatFilePath;
|
QString m_chatFilePath;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -160,8 +160,8 @@ void ClientInterface::sendMessage(
|
|||||||
|
|
||||||
Context::ChangesManager::instance().archiveAllNonArchivedEdits();
|
Context::ChangesManager::instance().archiveAllNonArchivedEdits();
|
||||||
|
|
||||||
QList<QString> imageFiles;
|
QStringList imageFiles;
|
||||||
QList<QString> textFiles;
|
QStringList textFiles;
|
||||||
for (const QString &filePath : attachments) {
|
for (const QString &filePath : attachments) {
|
||||||
if (isImageFile(filePath))
|
if (isImageFile(filePath))
|
||||||
imageFiles.append(filePath);
|
imageFiles.append(filePath);
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
|||||||
if (requestId.isEmpty()) {
|
if (requestId.isEmpty()) {
|
||||||
QString error = QString("Failed to start completion request for agent '%1': %2")
|
QString error = QString("Failed to start completion request for agent '%1': %2")
|
||||||
.arg(agentName, session->lastError().message);
|
.arg(agentName, session->lastError().message);
|
||||||
session->deleteLater();
|
m_sessionManager.removeSession(session);
|
||||||
LOG_MESSAGE(error);
|
LOG_MESSAGE(error);
|
||||||
sendErrorResponse(request, error);
|
sendErrorResponse(request, error);
|
||||||
return;
|
return;
|
||||||
@@ -304,7 +304,7 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
|||||||
|
|
||||||
QString LLMClientInterface::pickCompletionAgent(const QString &filePath) const
|
QString LLMClientInterface::pickCompletionAgent(const QString &filePath) const
|
||||||
{
|
{
|
||||||
const QStringList roster = Settings::PipelinesConfig::load().rosters.codeCompletion;
|
const QStringList roster = Settings::PipelinesConfig::loadCached().rosters.codeCompletion;
|
||||||
if (roster.isEmpty())
|
if (roster.isEmpty())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ struct RefactorResult
|
|||||||
Utils::Text::Range insertRange;
|
Utils::Text::Range insertRange;
|
||||||
bool success;
|
bool success;
|
||||||
QString errorMessage;
|
QString errorMessage;
|
||||||
TextEditor::TextEditorWidget *editor{nullptr};
|
QPointer<TextEditor::TextEditorWidget> editor;
|
||||||
};
|
};
|
||||||
|
|
||||||
class QuickRefactorHandler : public QObject
|
class QuickRefactorHandler : public QObject
|
||||||
@@ -73,7 +73,7 @@ private:
|
|||||||
QPointer<SessionManager> m_sessionManager;
|
QPointer<SessionManager> m_sessionManager;
|
||||||
QPointer<AgentFactory> m_agentFactory;
|
QPointer<AgentFactory> m_agentFactory;
|
||||||
QHash<QString, RequestContext> m_activeRequests;
|
QHash<QString, RequestContext> m_activeRequests;
|
||||||
TextEditor::TextEditorWidget *m_currentEditor;
|
QPointer<TextEditor::TextEditorWidget> m_currentEditor;
|
||||||
Utils::Text::Range m_currentRange;
|
Utils::Text::Range m_currentRange;
|
||||||
bool m_isRefactoringInProgress;
|
bool m_isRefactoringInProgress;
|
||||||
QString m_lastRequestId;
|
QString m_lastRequestId;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public:
|
|||||||
QString lineContent;
|
QString lineContent;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum FileEditStatus { Pending, Applied, Rejected, Archived };
|
enum FileEditStatus : int { Pending, Applied, Rejected, Archived };
|
||||||
|
|
||||||
struct DiffHunk
|
struct DiffHunk
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ public:
|
|||||||
int lineNumber, int cursorPosition, const Settings::CodeCompletionSettings &settings) const;
|
int lineNumber, int cursorPosition, const Settings::CodeCompletionSettings &settings) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TextEditor::TextDocument *m_textDocument;
|
TextEditor::TextDocument *m_textDocument = nullptr;
|
||||||
QTextDocument *m_document;
|
QTextDocument *m_document;
|
||||||
QString m_mimeType;
|
QString m_mimeType;
|
||||||
QString m_filePath;
|
QString m_filePath;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class Pill : public QLabel
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum Kind { Neutral, Accent, On, Off, User, Tag, Active, Match };
|
enum Kind : int { Neutral, Accent, On, Off, User, Tag, Active, Match };
|
||||||
|
|
||||||
explicit Pill(Kind kind, const QString &text = {}, QWidget *parent = nullptr);
|
explicit Pill(Kind kind, const QString &text = {}, QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
@@ -203,6 +204,23 @@ PipelinesLoadResult PipelinesConfig::load()
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PipelinesLoadResult PipelinesConfig::loadCached()
|
||||||
|
{
|
||||||
|
static PipelinesLoadResult cached;
|
||||||
|
static QDateTime cachedMTime;
|
||||||
|
static bool valid = false;
|
||||||
|
|
||||||
|
const QFileInfo info(filePath());
|
||||||
|
const QDateTime mtime = info.exists() ? info.lastModified() : QDateTime();
|
||||||
|
if (valid && mtime == cachedMTime)
|
||||||
|
return cached;
|
||||||
|
|
||||||
|
cached = load();
|
||||||
|
cachedMTime = mtime;
|
||||||
|
valid = true;
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
bool PipelinesConfig::save(const PipelineRosters &rosters, QString *errorOut)
|
bool PipelinesConfig::save(const PipelineRosters &rosters, QString *errorOut)
|
||||||
{
|
{
|
||||||
const QString path = filePath();
|
const QString path = filePath();
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] static PipelinesLoadResult load();
|
[[nodiscard]] static PipelinesLoadResult load();
|
||||||
|
|
||||||
|
[[nodiscard]] static PipelinesLoadResult loadCached();
|
||||||
|
|
||||||
[[nodiscard]] static bool save(const PipelineRosters &rosters, QString *errorOut = nullptr);
|
[[nodiscard]] static bool save(const PipelineRosters &rosters, QString *errorOut = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] static bool validate(
|
[[nodiscard]] static bool validate(
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ TEST(SystemPromptBuilderTest, IdenticalSetLayerEmitsNoSignal)
|
|||||||
QSignalSpy spy(&builder, &SystemPromptBuilder::layersChanged);
|
QSignalSpy spy(&builder, &SystemPromptBuilder::layersChanged);
|
||||||
builder.setLayer(QStringLiteral("a"), QStringLiteral("A"), 10);
|
builder.setLayer(QStringLiteral("a"), QStringLiteral("A"), 10);
|
||||||
|
|
||||||
EXPECT_EQ(spy.count(), 0);
|
EXPECT_EQ(spy.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SystemPromptBuilderTest, ChangingSetLayerEmitsSignal)
|
TEST(SystemPromptBuilderTest, ChangingSetLayerEmitsSignal)
|
||||||
@@ -103,7 +103,7 @@ TEST(SystemPromptBuilderTest, ChangingSetLayerEmitsSignal)
|
|||||||
QSignalSpy spy(&builder, &SystemPromptBuilder::layersChanged);
|
QSignalSpy spy(&builder, &SystemPromptBuilder::layersChanged);
|
||||||
builder.setLayer(QStringLiteral("a"), QStringLiteral("A"), 20);
|
builder.setLayer(QStringLiteral("a"), QStringLiteral("A"), 20);
|
||||||
|
|
||||||
EXPECT_EQ(spy.count(), 1);
|
EXPECT_EQ(spy.size(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SystemPromptBuilderTest, ClearLayerRemovesAndSignals)
|
TEST(SystemPromptBuilderTest, ClearLayerRemovesAndSignals)
|
||||||
@@ -115,7 +115,7 @@ TEST(SystemPromptBuilderTest, ClearLayerRemovesAndSignals)
|
|||||||
builder.clearLayer(QStringLiteral("a"));
|
builder.clearLayer(QStringLiteral("a"));
|
||||||
|
|
||||||
EXPECT_TRUE(builder.isEmpty());
|
EXPECT_TRUE(builder.isEmpty());
|
||||||
EXPECT_EQ(spy.count(), 1);
|
EXPECT_EQ(spy.size(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SystemPromptBuilderTest, ClearMissingLayerEmitsNoSignal)
|
TEST(SystemPromptBuilderTest, ClearMissingLayerEmitsNoSignal)
|
||||||
@@ -127,7 +127,7 @@ TEST(SystemPromptBuilderTest, ClearMissingLayerEmitsNoSignal)
|
|||||||
builder.clearLayer(QStringLiteral("nope"));
|
builder.clearLayer(QStringLiteral("nope"));
|
||||||
|
|
||||||
EXPECT_FALSE(builder.isEmpty());
|
EXPECT_FALSE(builder.isEmpty());
|
||||||
EXPECT_EQ(spy.count(), 0);
|
EXPECT_EQ(spy.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SystemPromptBuilderTest, ClearEmptiesAndSignals)
|
TEST(SystemPromptBuilderTest, ClearEmptiesAndSignals)
|
||||||
@@ -140,7 +140,7 @@ TEST(SystemPromptBuilderTest, ClearEmptiesAndSignals)
|
|||||||
builder.clear();
|
builder.clear();
|
||||||
|
|
||||||
EXPECT_TRUE(builder.isEmpty());
|
EXPECT_TRUE(builder.isEmpty());
|
||||||
EXPECT_EQ(spy.count(), 1);
|
EXPECT_EQ(spy.size(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SystemPromptBuilderTest, ClearWhenAlreadyEmptyEmitsNoSignal)
|
TEST(SystemPromptBuilderTest, ClearWhenAlreadyEmptyEmitsNoSignal)
|
||||||
@@ -150,5 +150,5 @@ TEST(SystemPromptBuilderTest, ClearWhenAlreadyEmptyEmitsNoSignal)
|
|||||||
QSignalSpy spy(&builder, &SystemPromptBuilder::layersChanged);
|
QSignalSpy spy(&builder, &SystemPromptBuilder::layersChanged);
|
||||||
builder.clear();
|
builder.clear();
|
||||||
|
|
||||||
EXPECT_EQ(spy.count(), 0);
|
EXPECT_EQ(spy.size(), 0);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user