From d2b28093a604486d76de25466a6775837fa52448 Mon Sep 17 00:00:00 2001 From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:03:22 +0200 Subject: [PATCH] feat: Improve showing tools in chat (#235) --- ChatView/CMakeLists.txt | 1 + ChatView/ChatModel.cpp | 62 ++++++++++++- ChatView/ChatModel.hpp | 10 ++- ChatView/ClientInterface.cpp | 12 +++ ChatView/qml/ChatItem.qml | 3 - ChatView/qml/RootItem.qml | 49 +++++++---- ChatView/qml/ToolStatusItem.qml | 126 +++++++++++++++++++++++++++ ChatView/qml/dialog/CodeBlock.qml | 130 ++++++++++++++++++++-------- llmcore/BaseTool.hpp | 1 + llmcore/Provider.hpp | 8 ++ providers/ClaudeProvider.cpp | 15 ++++ providers/LMStudioProvider.cpp | 15 ++++ providers/LlamaCppProvider.cpp | 15 ++++ providers/MistralAIProvider.cpp | 15 ++++ providers/OpenAICompatProvider.cpp | 15 ++++ providers/OpenAIProvider.cpp | 15 ++++ tools/ListProjectFilesTool.cpp | 5 ++ tools/ListProjectFilesTool.hpp | 1 + tools/ReadProjectFileByNameTool.cpp | 5 ++ tools/ReadProjectFileByNameTool.hpp | 1 + tools/ReadVisibleFilesTool.cpp | 5 ++ tools/ReadVisibleFilesTool.hpp | 1 + tools/ToolsFactory.cpp | 5 ++ tools/ToolsFactory.hpp | 1 + tools/ToolsManager.cpp | 5 ++ tools/ToolsManager.hpp | 2 + 26 files changed, 464 insertions(+), 59 deletions(-) create mode 100644 ChatView/qml/ToolStatusItem.qml diff --git a/ChatView/CMakeLists.txt b/ChatView/CMakeLists.txt index bb1976f..262a99c 100644 --- a/ChatView/CMakeLists.txt +++ b/ChatView/CMakeLists.txt @@ -16,6 +16,7 @@ qt_add_qml_module(QodeAssistChatView qml/parts/TopBar.qml qml/parts/BottomBar.qml qml/parts/AttachedFilesPlace.qml + qml/ToolStatusItem.qml RESOURCES icons/attach-file-light.svg diff --git a/ChatView/ChatModel.cpp b/ChatView/ChatModel.cpp index e437ab2..7018675 100644 --- a/ChatView/ChatModel.cpp +++ b/ChatView/ChatModel.cpp @@ -23,6 +23,7 @@ #include #include "ChatAssistantSettings.hpp" +#include "Logger.hpp" namespace QodeAssist::Chat { @@ -91,10 +92,12 @@ void ChatModel::addMessage( } } - 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) { Message &lastMessage = m_messages.last(); lastMessage.content = content; lastMessage.attachments = attachments; + LOG_MESSAGE(QString("Updated message: role=%1, id=%2").arg(role).arg(id)); emit dataChanged(index(m_messages.size() - 1), index(m_messages.size() - 1)); } else { beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size()); @@ -102,6 +105,10 @@ void ChatModel::addMessage( newMessage.attachments = attachments; m_messages.append(newMessage); endInsertRows(); + LOG_MESSAGE(QString("Added new message: role=%1, id=%2, index=%3") + .arg(role) + .arg(id) + .arg(m_messages.size() - 1)); } } @@ -224,4 +231,57 @@ void ChatModel::resetModelTo(int index) } } +void ChatModel::addToolExecutionStatus( + const QString &requestId, const QString &toolId, const QString &toolName) +{ + QString content = toolName; + + LOG_MESSAGE(QString("Adding tool execution status: requestId=%1, toolId=%2, toolName=%3") + .arg(requestId, toolId, toolName)); + + if (!m_messages.isEmpty() && !toolId.isEmpty() && m_messages.last().id == toolId + && m_messages.last().role == ChatRole::Tool) { + Message &lastMessage = m_messages.last(); + lastMessage.content = content; + LOG_MESSAGE(QString("Updated existing tool message at index %1").arg(m_messages.size() - 1)); + emit dataChanged(index(m_messages.size() - 1), index(m_messages.size() - 1)); + } else { + beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size()); + Message newMessage{ChatRole::Tool, content, toolId}; + m_messages.append(newMessage); + endInsertRows(); + LOG_MESSAGE(QString("Created new tool message at index %1 with toolId=%2") + .arg(m_messages.size() - 1) + .arg(toolId)); + } +} + +void ChatModel::updateToolResult( + const QString &requestId, const QString &toolId, const QString &toolName, const QString &result) +{ + if (m_messages.isEmpty() || toolId.isEmpty()) { + LOG_MESSAGE(QString("Cannot update tool result: messages empty=%1, toolId empty=%2") + .arg(m_messages.isEmpty()) + .arg(toolId.isEmpty())); + return; + } + + LOG_MESSAGE( + QString("Updating tool result: requestId=%1, toolId=%2, toolName=%3, result length=%4") + .arg(requestId, toolId, toolName) + .arg(result.length())); + + for (int i = m_messages.size() - 1; i >= 0; --i) { + if (m_messages[i].id == toolId && m_messages[i].role == ChatRole::Tool) { + m_messages[i].content = toolName + "\n" + result; + emit dataChanged(index(i), index(i)); + LOG_MESSAGE(QString("Updated tool result at index %1").arg(i)); + return; + } + } + + LOG_MESSAGE(QString("WARNING: Tool message with requestId=%1 toolId=%2 not found!") + .arg(requestId, toolId)); +} + } // namespace QodeAssist::Chat diff --git a/ChatView/ChatModel.hpp b/ChatView/ChatModel.hpp index a6580d1..b25c09a 100644 --- a/ChatView/ChatModel.hpp +++ b/ChatView/ChatModel.hpp @@ -37,10 +37,11 @@ class ChatModel : public QAbstractListModel QML_ELEMENT public: - enum ChatRole { System, User, Assistant }; + enum ChatRole { System, User, Assistant, Tool }; Q_ENUM(ChatRole) enum Roles { RoleType = Qt::UserRole, Content, Attachments }; + Q_ENUM(Roles) struct Message { @@ -75,6 +76,13 @@ public: Q_INVOKABLE void resetModelTo(int index); + void addToolExecutionStatus( + const QString &requestId, const QString &toolId, const QString &toolName); + void updateToolResult( + const QString &requestId, + const QString &toolId, + const QString &toolName, + const QString &result); signals: void tokensThresholdChanged(); void modelReseted(); diff --git a/ChatView/ClientInterface.cpp b/ChatView/ClientInterface.cpp index 0340d7e..358cb60 100644 --- a/ChatView/ClientInterface.cpp +++ b/ChatView/ClientInterface.cpp @@ -139,6 +139,18 @@ void ClientInterface::sendMessage( this, &ClientInterface::handleRequestFailed, Qt::UniqueConnection); + connect( + provider, + &LLMCore::Provider::toolExecutionStarted, + m_chatModel, + &ChatModel::addToolExecutionStatus, + Qt::UniqueConnection); + connect( + provider, + &LLMCore::Provider::toolExecutionCompleted, + m_chatModel, + &ChatModel::updateToolResult, + Qt::UniqueConnection); provider->sendRequest(requestId, config.url, config.providerRequest); } diff --git a/ChatView/qml/ChatItem.qml b/ChatView/qml/ChatItem.qml index 4c99a37..6bba114 100644 --- a/ChatView/qml/ChatItem.qml +++ b/ChatView/qml/ChatItem.qml @@ -48,7 +48,6 @@ Rectangle { property bool isUserMessage: false property int messageIndex: -1 - property real listViewContentY: 0 signal resetChatToMessage(int index) @@ -104,8 +103,6 @@ Rectangle { id: codeBlockComponent CodeBlockComponent { itemData: msgCreatorDelegate.modelData - blockStart: root.y + msgCreatorDelegate.y - currentContentY: root.listViewContentY } } } diff --git a/ChatView/qml/RootItem.qml b/ChatView/qml/RootItem.qml index 7ee9b25..268e5da 100644 --- a/ChatView/qml/RootItem.qml +++ b/ChatView/qml/RootItem.qml @@ -95,27 +95,14 @@ ChatRootView { boundsBehavior: Flickable.StopAtBounds cacheBuffer: 2000 - delegate: ChatItem { + + delegate: Loader { required property var model required property int index width: ListView.view.width - scroll.width - msgModel: root.chatModel.processMessageContent(model.content) - messageAttachments: model.attachments - isUserMessage: model.roleType === ChatModel.User - messageIndex: index - listViewContentY: chatListView.contentY - textFontFamily: root.textFontFamily - codeFontFamily: root.codeFontFamily - codeFontSize: root.codeFontSize - textFontSize: root.textFontSize - textFormat: root.textFormat - onResetChatToMessage: function(index) { - messageInput.text = model.content - messageInput.cursorPosition = model.content.length - root.chatModel.resetModelTo(index) - } + sourceComponent: model.roleType === ChatModel.Tool ? toolMessageComponent : chatItemComponent } header: Item { @@ -136,6 +123,36 @@ ChatRootView { root.scrollToBottom() } } + + Component { + id: chatItemComponent + + ChatItem { + msgModel: root.chatModel.processMessageContent(model.content) + messageAttachments: model.attachments + isUserMessage: model.roleType === ChatModel.User + messageIndex: index + textFontFamily: root.textFontFamily + codeFontFamily: root.codeFontFamily + codeFontSize: root.codeFontSize + textFontSize: root.textFontSize + textFormat: root.textFormat + + onResetChatToMessage: function(idx) { + messageInput.text = model.content + messageInput.cursorPosition = model.content.length + root.chatModel.resetModelTo(idx) + } + } + } + + Component { + id: toolMessageComponent + + ToolStatusItem { + toolContent: model.content + } + } } ScrollView { diff --git a/ChatView/qml/ToolStatusItem.qml b/ChatView/qml/ToolStatusItem.qml new file mode 100644 index 0000000..9d9f072 --- /dev/null +++ b/ChatView/qml/ToolStatusItem.qml @@ -0,0 +1,126 @@ +/* + * Copyright (C) 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 . + */ + +import QtQuick + +Rectangle { + id: root + + property string toolContent: "" + property bool expanded: false + + readonly property int firstNewline: toolContent.indexOf('\n') + readonly property string toolName: firstNewline > 0 ? toolContent.substring(0, firstNewline) : toolContent + readonly property string toolResult: firstNewline > 0 ? toolContent.substring(firstNewline + 1) : "" + + radius: 6 + color: palette.base + clip: true + + Behavior on implicitHeight { + NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } + } + + MouseArea { + id: header + + width: parent.width + height: headerRow.height + 10 + cursorShape: Qt.PointingHandCursor + onClicked: root.expanded = !root.expanded + + Row { + id: headerRow + + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: 10 + } + width: parent.width + spacing: 8 + + Text { + text: qsTr("Tool: %1").arg(root.toolName) + font.pixelSize: 13 + font.bold: true + color: palette.text + } + + Text { + anchors.verticalCenter: parent.verticalCenter + text: root.expanded ? "▼" : "▶" + font.pixelSize: 10 + color: palette.mid + } + } + } + + Column { + id: contentColumn + + anchors { + left: parent.left + right: parent.right + top: header.bottom + margins: 10 + } + spacing: 8 + + Text { + id: resultText + + width: parent.width + text: root.toolResult + wrapMode: Text.Wrap + font.family: "monospace" + font.pixelSize: 11 + color: palette.text + } + } + + Rectangle { + id: messageMarker + + anchors.verticalCenter: parent.verticalCenter + width: 3 + height: root.height - root.radius + color: root.color.hslLightness > 0.5 ? Qt.darker(palette.alternateBase, 1.3) + : Qt.lighter(palette.alternateBase, 1.3) + radius: root.radius + } + + + states: [ + State { + when: !root.expanded + PropertyChanges { + target: root + implicitHeight: header.height + } + }, + State { + when: root.expanded + PropertyChanges { + target: root + implicitHeight: header.height + contentColumn.height + 20 + } + } + ] +} diff --git a/ChatView/qml/dialog/CodeBlock.qml b/ChatView/qml/dialog/CodeBlock.qml index 30bae28..47fb19d 100644 --- a/ChatView/qml/dialog/CodeBlock.qml +++ b/ChatView/qml/dialog/CodeBlock.qml @@ -27,27 +27,11 @@ Rectangle { property string code: "" property string language: "" - - property real currentContentY: 0 - property real blockStart: 0 + property bool expanded: false property alias codeFontFamily: codeText.font.family property alias codeFontSize: codeText.font.pointSize - - readonly property real buttonTopMargin: 5 - readonly property real blockEnd: blockStart + root.height - readonly property real maxButtonOffset: Math.max(0, root.height - copyButton.height - buttonTopMargin) - - readonly property real buttonPosition: { - if (currentContentY > blockEnd) { - return buttonTopMargin; - } - else if (currentContentY > blockStart) { - let offset = currentContentY - blockStart; - return Math.min(offset, maxButtonOffset); - } - return buttonTopMargin; - } + readonly property real collapsedHeight: copyButton.height + 10 color: palette.alternateBase border.color: root.color.hslLightness > 0.5 ? Qt.darker(root.color, 1.3) @@ -55,16 +39,65 @@ Rectangle { border.width: 2 radius: 4 implicitWidth: parent.width - implicitHeight: codeText.implicitHeight + 20 + clip: true + + Behavior on implicitHeight { + NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } + } ChatUtils { id: utils } + HoverHandler { + id: hoverHandler + enabled: true + } + + MouseArea { + id: header + + width: parent.width + height: root.collapsedHeight + cursorShape: Qt.PointingHandCursor + onClicked: root.expanded = !root.expanded + + Row { + id: headerRow + + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: 10 + } + spacing: 6 + + Text { + text: root.language ? qsTr("Code (%1)").arg(root.language) : + qsTr("Code") + font.pixelSize: 12 + font.bold: true + color: palette.text + } + + Text { + anchors.verticalCenter: parent.verticalCenter + text: root.expanded ? "▼" : "▶" + font.pixelSize: 10 + color: palette.mid + } + } + } + TextEdit { id: codeText - anchors.fill: parent - anchors.margins: 10 + + anchors { + left: parent.left + right: parent.right + top: header.bottom + margins: 10 + } text: root.code readOnly: true selectByMouse: true @@ -73,29 +106,33 @@ Rectangle { selectionColor: palette.highlight } - TextEdit { - anchors.top: parent.top - anchors.right: parent.right - anchors.margins: 5 - readOnly: true - selectByMouse: true - text: root.language - color: root.color.hslLightness > 0.5 ? Qt.darker(root.color, 1.1) - : Qt.lighter(root.color, 1.1) - font.pointSize: codeText.font.pointSize - 4 - } - QoAButton { id: copyButton - anchors { - top: parent.top - topMargin: root.buttonPosition - right: parent.right - rightMargin: root.buttonTopMargin + anchors.right: parent.right + anchors.rightMargin: 5 + + y: { + if (!hoverHandler.hovered || !root.expanded) { + return 5 + } + + let mouseY = hoverHandler.point.position.y + let minY = header.height + 5 + let maxY = root.height - copyButton.height - 5 + return Math.max(minY, Math.min(mouseY - copyButton.height / 2, maxY)) + } + + Behavior on y { + NumberAnimation { duration: 100; easing.type: Easing.OutQuad } + } + + Behavior on opacity { + NumberAnimation { duration: 150 } } text: qsTr("Copy") + onClicked: { utils.copyToClipboard(root.code) text = qsTr("Copied") @@ -108,4 +145,21 @@ Rectangle { onTriggered: parent.text = qsTr("Copy") } } + + states: [ + State { + when: !root.expanded + PropertyChanges { + target: root + implicitHeight: root.collapsedHeight + } + }, + State { + when: root.expanded + PropertyChanges { + target: root + implicitHeight: header.height + codeText.implicitHeight + 10 + } + } + ] } diff --git a/llmcore/BaseTool.hpp b/llmcore/BaseTool.hpp index d01e45c..3320695 100644 --- a/llmcore/BaseTool.hpp +++ b/llmcore/BaseTool.hpp @@ -37,6 +37,7 @@ public: ~BaseTool() override = default; virtual QString name() const = 0; + virtual QString stringName() const = 0; virtual QString description() const = 0; virtual QJsonObject getDefinition(ToolSchemaFormat format) const = 0; diff --git a/llmcore/Provider.hpp b/llmcore/Provider.hpp index d37107f..d4aa86a 100644 --- a/llmcore/Provider.hpp +++ b/llmcore/Provider.hpp @@ -83,6 +83,14 @@ signals: void fullResponseReceived( const QodeAssist::LLMCore::RequestID &requestId, const QString &fullText); void requestFailed(const QodeAssist::LLMCore::RequestID &requestId, const QString &error); +signals: + void toolExecutionStarted( + const QString &requestId, const QString &toolId, const QString &toolName); + void toolExecutionCompleted( + const QString &requestId, + const QString &toolId, + const QString &toolName, + const QString &result); protected: QJsonObject parseEventLine(const QString &line); diff --git a/providers/ClaudeProvider.cpp b/providers/ClaudeProvider.cpp index a7e6657..fc77e67 100644 --- a/providers/ClaudeProvider.cpp +++ b/providers/ClaudeProvider.cpp @@ -281,6 +281,19 @@ void ClaudeProvider::onToolExecutionComplete( LOG_MESSAGE(QString("Tool execution complete for Claude request %1").arg(requestId)); + for (auto it = toolResults.begin(); it != toolResults.end(); ++it) { + ClaudeMessage *message = m_messages[requestId]; + auto toolContent = message->getCurrentToolUseContent(); + for (auto tool : toolContent) { + if (tool->id() == it.key()) { + auto toolStringName = m_toolsManager->toolsFactory()->getStringName(tool->name()); + emit toolExecutionCompleted( + requestId, tool->id(), toolStringName, toolResults[tool->id()]); + break; + } + } + } + ClaudeMessage *message = m_messages[requestId]; QJsonObject continuationRequest = m_originalRequests[requestId]; QJsonArray messages = continuationRequest["messages"].toArray(); @@ -382,6 +395,8 @@ void ClaudeProvider::handleMessageComplete(const QString &requestId) } for (auto toolContent : toolUseContent) { + auto toolStringName = m_toolsManager->toolsFactory()->getStringName(toolContent->name()); + emit toolExecutionStarted(requestId, toolContent->id(), toolStringName); m_toolsManager->executeToolCall( requestId, toolContent->id(), toolContent->name(), toolContent->input()); } diff --git a/providers/LMStudioProvider.cpp b/providers/LMStudioProvider.cpp index af0a814..1620430 100644 --- a/providers/LMStudioProvider.cpp +++ b/providers/LMStudioProvider.cpp @@ -270,6 +270,19 @@ void LMStudioProvider::onToolExecutionComplete( LOG_MESSAGE(QString("Tool execution complete for LMStudio request %1").arg(requestId)); + for (auto it = toolResults.begin(); it != toolResults.end(); ++it) { + OpenAIMessage *message = m_messages[requestId]; + auto toolContent = message->getCurrentToolUseContent(); + for (auto tool : toolContent) { + if (tool->id() == it.key()) { + auto toolStringName = m_toolsManager->toolsFactory()->getStringName(tool->name()); + emit toolExecutionCompleted( + requestId, tool->id(), toolStringName, toolResults[tool->id()]); + break; + } + } + } + OpenAIMessage *message = m_messages[requestId]; QJsonObject continuationRequest = m_originalRequests[requestId]; QJsonArray messages = continuationRequest["messages"].toArray(); @@ -368,6 +381,8 @@ void LMStudioProvider::handleMessageComplete(const QString &requestId) } for (auto toolContent : toolUseContent) { + auto toolStringName = m_toolsManager->toolsFactory()->getStringName(toolContent->name()); + emit toolExecutionStarted(requestId, toolContent->id(), toolStringName); m_toolsManager->executeToolCall( requestId, toolContent->id(), toolContent->name(), toolContent->input()); } diff --git a/providers/LlamaCppProvider.cpp b/providers/LlamaCppProvider.cpp index 6a5a005..0b74cb1 100644 --- a/providers/LlamaCppProvider.cpp +++ b/providers/LlamaCppProvider.cpp @@ -274,6 +274,19 @@ void LlamaCppProvider::onToolExecutionComplete( LOG_MESSAGE(QString("Tool execution complete for llama.cpp request %1").arg(requestId)); + for (auto it = toolResults.begin(); it != toolResults.end(); ++it) { + OpenAIMessage *message = m_messages[requestId]; + auto toolContent = message->getCurrentToolUseContent(); + for (auto tool : toolContent) { + if (tool->id() == it.key()) { + auto toolStringName = m_toolsManager->toolsFactory()->getStringName(tool->name()); + emit toolExecutionCompleted( + requestId, tool->id(), toolStringName, toolResults[tool->id()]); + break; + } + } + } + OpenAIMessage *message = m_messages[requestId]; QJsonObject continuationRequest = m_originalRequests[requestId]; QJsonArray messages = continuationRequest["messages"].toArray(); @@ -372,6 +385,8 @@ void LlamaCppProvider::handleMessageComplete(const QString &requestId) } for (auto toolContent : toolUseContent) { + auto toolStringName = m_toolsManager->toolsFactory()->getStringName(toolContent->name()); + emit toolExecutionStarted(requestId, toolContent->id(), toolStringName); m_toolsManager->executeToolCall( requestId, toolContent->id(), toolContent->name(), toolContent->input()); } diff --git a/providers/MistralAIProvider.cpp b/providers/MistralAIProvider.cpp index 99318de..d2bd071 100644 --- a/providers/MistralAIProvider.cpp +++ b/providers/MistralAIProvider.cpp @@ -291,6 +291,19 @@ void MistralAIProvider::onToolExecutionComplete( LOG_MESSAGE(QString("Tool execution complete for Mistral request %1").arg(requestId)); + for (auto it = toolResults.begin(); it != toolResults.end(); ++it) { + OpenAIMessage *message = m_messages[requestId]; + auto toolContent = message->getCurrentToolUseContent(); + for (auto tool : toolContent) { + if (tool->id() == it.key()) { + auto toolStringName = m_toolsManager->toolsFactory()->getStringName(tool->name()); + emit toolExecutionCompleted( + requestId, tool->id(), toolStringName, toolResults[tool->id()]); + break; + } + } + } + OpenAIMessage *message = m_messages[requestId]; QJsonObject continuationRequest = m_originalRequests[requestId]; QJsonArray messages = continuationRequest["messages"].toArray(); @@ -389,6 +402,8 @@ void MistralAIProvider::handleMessageComplete(const QString &requestId) } for (auto toolContent : toolUseContent) { + auto toolStringName = m_toolsManager->toolsFactory()->getStringName(toolContent->name()); + emit toolExecutionStarted(requestId, toolContent->id(), toolStringName); m_toolsManager->executeToolCall( requestId, toolContent->id(), toolContent->name(), toolContent->input()); } diff --git a/providers/OpenAICompatProvider.cpp b/providers/OpenAICompatProvider.cpp index a7bf5a9..1178afb 100644 --- a/providers/OpenAICompatProvider.cpp +++ b/providers/OpenAICompatProvider.cpp @@ -248,6 +248,19 @@ void OpenAICompatProvider::onToolExecutionComplete( LOG_MESSAGE(QString("Tool execution complete for OpenAICompat request %1").arg(requestId)); + for (auto it = toolResults.begin(); it != toolResults.end(); ++it) { + OpenAIMessage *message = m_messages[requestId]; + auto toolContent = message->getCurrentToolUseContent(); + for (auto tool : toolContent) { + if (tool->id() == it.key()) { + auto toolStringName = m_toolsManager->toolsFactory()->getStringName(tool->name()); + emit toolExecutionCompleted( + requestId, tool->id(), toolStringName, toolResults[tool->id()]); + break; + } + } + } + OpenAIMessage *message = m_messages[requestId]; QJsonObject continuationRequest = m_originalRequests[requestId]; QJsonArray messages = continuationRequest["messages"].toArray(); @@ -346,6 +359,8 @@ void OpenAICompatProvider::handleMessageComplete(const QString &requestId) } for (auto toolContent : toolUseContent) { + auto toolStringName = m_toolsManager->toolsFactory()->getStringName(toolContent->name()); + emit toolExecutionStarted(requestId, toolContent->id(), toolStringName); m_toolsManager->executeToolCall( requestId, toolContent->id(), toolContent->name(), toolContent->input()); } diff --git a/providers/OpenAIProvider.cpp b/providers/OpenAIProvider.cpp index db63c3a..a39a04c 100644 --- a/providers/OpenAIProvider.cpp +++ b/providers/OpenAIProvider.cpp @@ -284,6 +284,19 @@ void OpenAIProvider::onToolExecutionComplete( LOG_MESSAGE(QString("Tool execution complete for OpenAI request %1").arg(requestId)); + for (auto it = toolResults.begin(); it != toolResults.end(); ++it) { + OpenAIMessage *message = m_messages[requestId]; + auto toolContent = message->getCurrentToolUseContent(); + for (auto tool : toolContent) { + if (tool->id() == it.key()) { + auto toolStringName = m_toolsManager->toolsFactory()->getStringName(tool->name()); + emit toolExecutionCompleted( + requestId, tool->id(), toolStringName, toolResults[tool->id()]); + break; + } + } + } + OpenAIMessage *message = m_messages[requestId]; QJsonObject continuationRequest = m_originalRequests[requestId]; QJsonArray messages = continuationRequest["messages"].toArray(); @@ -382,6 +395,8 @@ void OpenAIProvider::handleMessageComplete(const QString &requestId) } for (auto toolContent : toolUseContent) { + auto toolStringName = m_toolsManager->toolsFactory()->getStringName(toolContent->name()); + emit toolExecutionStarted(requestId, toolContent->id(), toolStringName); m_toolsManager->executeToolCall( requestId, toolContent->id(), toolContent->name(), toolContent->input()); } diff --git a/tools/ListProjectFilesTool.cpp b/tools/ListProjectFilesTool.cpp index 733d7bd..d6288d0 100644 --- a/tools/ListProjectFilesTool.cpp +++ b/tools/ListProjectFilesTool.cpp @@ -41,6 +41,11 @@ QString ListProjectFilesTool::name() const return "list_project_files"; } +QString ListProjectFilesTool::stringName() const +{ + return {"Reading project files list"}; +} + QString ListProjectFilesTool::description() const { return "Get a list of all source files in the current project. " diff --git a/tools/ListProjectFilesTool.hpp b/tools/ListProjectFilesTool.hpp index 6163dcc..6cc58a9 100644 --- a/tools/ListProjectFilesTool.hpp +++ b/tools/ListProjectFilesTool.hpp @@ -32,6 +32,7 @@ public: explicit ListProjectFilesTool(QObject *parent = nullptr); QString name() const override; + QString stringName() const override; QString description() const override; QJsonObject getDefinition(LLMCore::ToolSchemaFormat format) const override; QFuture executeAsync(const QJsonObject &input = QJsonObject()) override; diff --git a/tools/ReadProjectFileByNameTool.cpp b/tools/ReadProjectFileByNameTool.cpp index 171d4c1..9ad235b 100644 --- a/tools/ReadProjectFileByNameTool.cpp +++ b/tools/ReadProjectFileByNameTool.cpp @@ -44,6 +44,11 @@ QString ReadProjectFileByNameTool::name() const return "read_project_file_by_name"; } +QString ReadProjectFileByNameTool::stringName() const +{ + return {"Reading project file by name"}; +} + QString ReadProjectFileByNameTool::description() const { return "Read the content of a specific file from the current project by providing its filename " diff --git a/tools/ReadProjectFileByNameTool.hpp b/tools/ReadProjectFileByNameTool.hpp index c308dbe..f0c1e18 100644 --- a/tools/ReadProjectFileByNameTool.hpp +++ b/tools/ReadProjectFileByNameTool.hpp @@ -31,6 +31,7 @@ public: explicit ReadProjectFileByNameTool(QObject *parent = nullptr); QString name() const override; + QString stringName() const override; QString description() const override; QJsonObject getDefinition(LLMCore::ToolSchemaFormat format) const override; QFuture executeAsync(const QJsonObject &input = QJsonObject()) override; diff --git a/tools/ReadVisibleFilesTool.cpp b/tools/ReadVisibleFilesTool.cpp index 42e9bea..7a34f12 100644 --- a/tools/ReadVisibleFilesTool.cpp +++ b/tools/ReadVisibleFilesTool.cpp @@ -39,6 +39,11 @@ QString ReadVisibleFilesTool::name() const return "read_visible_files"; } +QString ReadVisibleFilesTool::stringName() const +{ + return {"Reading currently opened and visible files in IDE editors"}; +} + QString ReadVisibleFilesTool::description() const { return "Read the content of all currently visible files in editor tabs. " diff --git a/tools/ReadVisibleFilesTool.hpp b/tools/ReadVisibleFilesTool.hpp index 84f6671..743c421 100644 --- a/tools/ReadVisibleFilesTool.hpp +++ b/tools/ReadVisibleFilesTool.hpp @@ -31,6 +31,7 @@ public: explicit ReadVisibleFilesTool(QObject *parent = nullptr); QString name() const override; + QString stringName() const override; QString description() const override; QJsonObject getDefinition(LLMCore::ToolSchemaFormat format) const override; QFuture executeAsync(const QJsonObject &input = QJsonObject()) override; diff --git a/tools/ToolsFactory.cpp b/tools/ToolsFactory.cpp index 4dc021f..0558e18 100644 --- a/tools/ToolsFactory.cpp +++ b/tools/ToolsFactory.cpp @@ -82,4 +82,9 @@ QJsonArray ToolsFactory::getToolsDefinitions(LLMCore::ToolSchemaFormat format) c return toolsArray; } +QString ToolsFactory::getStringName(const QString &name) const +{ + return m_tools.contains(name) ? m_tools.value(name)->stringName() : QString("Unknown tools"); +} + } // namespace QodeAssist::Tools diff --git a/tools/ToolsFactory.hpp b/tools/ToolsFactory.hpp index 14663a0..b7998c3 100644 --- a/tools/ToolsFactory.hpp +++ b/tools/ToolsFactory.hpp @@ -35,6 +35,7 @@ public: QList getAvailableTools() const; LLMCore::BaseTool *getToolByName(const QString &name) const; QJsonArray getToolsDefinitions(LLMCore::ToolSchemaFormat format) const; + QString getStringName(const QString &name) const; private: void registerTools(); diff --git a/tools/ToolsManager.cpp b/tools/ToolsManager.cpp index b664da3..f26f6b3 100644 --- a/tools/ToolsManager.cpp +++ b/tools/ToolsManager.cpp @@ -123,6 +123,11 @@ void ToolsManager::onToolFinished( } } +ToolsFactory *ToolsManager::toolsFactory() const +{ + return m_toolsFactory; +} + bool ToolsManager::isExecutionComplete(const QString &requestId) const { if (!m_pendingTools.contains(requestId)) { diff --git a/tools/ToolsManager.hpp b/tools/ToolsManager.hpp index 3ebd2e4..ee82182 100644 --- a/tools/ToolsManager.hpp +++ b/tools/ToolsManager.hpp @@ -56,6 +56,8 @@ public: QJsonArray getToolsDefinitions(ToolSchemaFormat format) const; void cleanupRequest(const QString &requestId); + ToolsFactory *toolsFactory() const; + signals: void toolExecutionComplete(const QString &requestId, const QHash &toolResults);