ProjectExplorer: Create AI task handlers dynamically

Change-Id: I7495bf7f4001031e84fc29eb852ddc9160d9bd94
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Christian Kandeler
2025-06-16 15:11:03 +02:00
parent 7a38a3c117
commit 37a34b4f15
3 changed files with 51 additions and 13 deletions

View File

@@ -244,7 +244,7 @@ CustomLanguageModels::CustomLanguageModels()
} // namespace } // namespace
QStringList availableLanguageModels() const QStringList availableLanguageModels()
{ {
return Utils::transform( return Utils::transform(
customLanguageModels().models.items(), [](const std::shared_ptr<BaseAspect> &aspect) { customLanguageModels().models.items(), [](const std::shared_ptr<BaseAspect> &aspect) {
@@ -261,6 +261,11 @@ CommandLine commandLineForLanguageModel(const QString &model)
return {}; return {};
} }
BaseAspect &customLanguageModelsContext()
{
return customLanguageModels();
}
namespace Internal { namespace Internal {
void setupCustomLanguageModels() void setupCustomLanguageModels()

View File

@@ -6,10 +6,13 @@
#include <utils/commandline.h> #include <utils/commandline.h>
namespace Utils { class BaseAspect; }
namespace Core { namespace Core {
CORE_EXPORT QStringList availableLanguageModels(); CORE_EXPORT const QStringList availableLanguageModels();
CORE_EXPORT Utils::CommandLine commandLineForLanguageModel(const QString &model); CORE_EXPORT Utils::CommandLine commandLineForLanguageModel(const QString &model);
CORE_EXPORT Utils::BaseAspect &customLanguageModelsContext();
namespace Internal { namespace Internal {

View File

@@ -18,18 +18,22 @@
#include <coreplugin/messagemanager.h> #include <coreplugin/messagemanager.h>
#include <coreplugin/vcsmanager.h> #include <coreplugin/vcsmanager.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/aspects.h>
#include <utils/qtcprocess.h> #include <utils/qtcprocess.h>
#include <utils/stringutils.h> #include <utils/stringutils.h>
#include <QHash> #include <QHash>
#include <QTimer> #include <QTimer>
#include <memory>
#include <vector>
using namespace Core; using namespace Core;
using namespace Utils; using namespace Utils;
namespace ProjectExplorer { namespace ProjectExplorer {
namespace { namespace {
static QObject *g_actionParent = nullptr; static QPointer<QObject> g_actionParent;
static Core::Context g_cmdContext; static Core::Context g_cmdContext;
static Internal::RegisterHandlerAction g_onCreateAction; static Internal::RegisterHandlerAction g_onCreateAction;
static Internal::GetHandlerTasks g_getTasks; static Internal::GetHandlerTasks g_getTasks;
@@ -248,11 +252,13 @@ private:
} }
}; };
// FIXME: There should be one handler per LLM
class ExplainWithAiHandler : public ITaskHandler class ExplainWithAiHandler : public ITaskHandler
{ {
public: public:
ExplainWithAiHandler() : ITaskHandler(createAction()) {} ExplainWithAiHandler(const QString &model)
: ITaskHandler(createAction(model))
, m_model(model)
{}
private: private:
bool canHandle(const Task &task) const override bool canHandle(const Task &task) const override
@@ -263,8 +269,8 @@ private:
void handle(const Task &task) override void handle(const Task &task) override
{ {
const QStringList llms = availableLanguageModels(); const CommandLine cmdLine = commandLineForLanguageModel(m_model);
QTC_ASSERT(!llms.isEmpty(), return); QTC_ASSERT(!cmdLine.isEmpty(), return);
QString prompt; QString prompt;
if (task.origin.isEmpty()) if (task.origin.isEmpty())
@@ -288,7 +294,7 @@ private:
} }
} }
const auto process = new Process; const auto process = new Process;
process->setCommand(commandLineForLanguageModel(llms.first())); process->setCommand(cmdLine);
process->setProcessMode(ProcessMode::Writer); process->setProcessMode(ProcessMode::Writer);
process->setTextChannelMode(Channel::Output, TextChannelMode::MultiLine); process->setTextChannelMode(Channel::Output, TextChannelMode::MultiLine);
process->setTextChannelMode(Channel::Error, TextChannelMode::MultiLine); process->setTextChannelMode(Channel::Error, TextChannelMode::MultiLine);
@@ -305,16 +311,40 @@ private:
process->closeWriteChannel(); process->closeWriteChannel();
}); });
QTimer::singleShot(60000, process, [process] { process->kill(); }); QTimer::singleShot(60000, process, [process] { process->kill(); });
MessageManager::writeDisrupting(Tr::tr("Querying LLM...")); MessageManager::writeDisrupting(Tr::tr("Querying %1...").arg(m_model));
process->start(); process->start();
} }
QAction *createAction() const QAction *createAction(const QString &model) const
{ {
const auto action = new QAction(Tr::tr("Get help from AI")); const auto action = new QAction(Tr::tr("Get help from %1").arg(model));
action->setToolTip(Tr::tr("Ask an AI to help with this issue.")); action->setToolTip(Tr::tr("Ask the %1 LLM to help with this issue.").arg(model));
return action; return action;
} }
const QString m_model;
};
class AiHandlersManager
{
public:
AiHandlersManager()
{
QObject::connect(&customLanguageModelsContext(), &BaseAspect::changed,
g_actionParent, [this] { reset(); });
reset();
}
private:
void reset()
{
m_aiTaskHandlers.clear();
for (const QString &model : availableLanguageModels())
m_aiTaskHandlers.emplace_back(std::make_unique<ExplainWithAiHandler>(model));
updateTaskHandlerActionsState();
}
std::vector<std::unique_ptr<ExplainWithAiHandler>> m_aiTaskHandlers;
}; };
} // namespace } // namespace
@@ -336,7 +366,7 @@ void setupTaskHandlers(
static const RemoveTaskHandler removeTaskHandler; static const RemoveTaskHandler removeTaskHandler;
static const ShowInEditorTaskHandler showInEditorTaskHandler; static const ShowInEditorTaskHandler showInEditorTaskHandler;
static const VcsAnnotateTaskHandler vcsAnnotateTaskHandler; static const VcsAnnotateTaskHandler vcsAnnotateTaskHandler;
static const ExplainWithAiHandler explainWithAiHandler; static const AiHandlersManager aiHandlersManager;
registerQueuedTaskHandlers(); registerQueuedTaskHandlers();
} }