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