forked from qt-creator/qt-creator
ProjectExplorer: Let users query a language model
... about entries in the issues pane. This is a simple proof of concept that just takes the first defined language model, provides it with a hard-coded prompt derived from the respective issue, and dumps the output into the general messages pane. Change-Id: I9a8c27be3ef16c75d007a74790e7ea0b1e6f1f04 Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -30,7 +30,7 @@ public:
|
|||||||
static const CustomLanguageModel &fromBaseAspect(const std::shared_ptr<BaseAspect> &aspect);
|
static const CustomLanguageModel &fromBaseAspect(const std::shared_ptr<BaseAspect> &aspect);
|
||||||
|
|
||||||
CustomLanguageModel();
|
CustomLanguageModel();
|
||||||
CommandLine commandLine(const QString &prompt);
|
CommandLine commandLine();
|
||||||
|
|
||||||
StringAspect name{this};
|
StringAspect name{this};
|
||||||
FilePathAspect executable{this};
|
FilePathAspect executable{this};
|
||||||
@@ -73,16 +73,6 @@ public:
|
|||||||
LanguageModelsListModel listModel{models};
|
LanguageModelsListModel listModel{models};
|
||||||
};
|
};
|
||||||
|
|
||||||
class PromptMacroExpander : public MacroExpander
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PromptMacroExpander(const QString &prompt)
|
|
||||||
{
|
|
||||||
registerVariable("Prompt", Tr::tr("The prompt to query the model with"),
|
|
||||||
[prompt] { return prompt; });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CustomLanguageModels &customLanguageModels()
|
CustomLanguageModels &customLanguageModels()
|
||||||
{
|
{
|
||||||
static CustomLanguageModels models;
|
static CustomLanguageModels models;
|
||||||
@@ -101,14 +91,9 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
CommandLine CustomLanguageModel::commandLine(const QString &prompt)
|
CommandLine CustomLanguageModel::commandLine()
|
||||||
{
|
{
|
||||||
MacroExpander * const exp = arguments.macroExpander();
|
return CommandLine(executable.effectiveBinary(), arguments.expandedValue(), CommandLine::Raw);
|
||||||
PromptMacroExpander expander(prompt);
|
|
||||||
arguments.setMacroExpander(&expander);
|
|
||||||
CommandLine cmdLine(executable.effectiveBinary(), arguments.expandedValue(), CommandLine::Raw);
|
|
||||||
arguments.setMacroExpander(exp);
|
|
||||||
return cmdLine;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomLanguageModel &CustomLanguageModel::fromBaseAspect(BaseAspect &aspect)
|
CustomLanguageModel &CustomLanguageModel::fromBaseAspect(BaseAspect &aspect)
|
||||||
@@ -136,12 +121,8 @@ CustomLanguageModel::CustomLanguageModel()
|
|||||||
arguments.setSettingsKey("Arguments");
|
arguments.setSettingsKey("Arguments");
|
||||||
arguments.setDisplayName(Tr::tr("Arguments"));
|
arguments.setDisplayName(Tr::tr("Arguments"));
|
||||||
arguments.setLabelText(Tr::tr("Arguments:"));
|
arguments.setLabelText(Tr::tr("Arguments:"));
|
||||||
arguments.setDefaultValue({"%{Prompt}"});
|
|
||||||
arguments.setDisplayStyle(StringAspect::LineEditDisplay);
|
arguments.setDisplayStyle(StringAspect::LineEditDisplay);
|
||||||
|
|
||||||
static PromptMacroExpander expander({});
|
|
||||||
arguments.setMacroExpander(&expander);
|
|
||||||
|
|
||||||
using namespace Layouting;
|
using namespace Layouting;
|
||||||
setLayouter([this] { return Form{name, br, executable, br, arguments}; });
|
setLayouter([this] { return Form{name, br, executable, br, arguments}; });
|
||||||
}
|
}
|
||||||
@@ -271,11 +252,11 @@ QStringList availableLanguageModels()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandLine commandLineForLanguageModel(const QString &model, const QString &prompt)
|
CommandLine commandLineForLanguageModel(const QString &model)
|
||||||
{
|
{
|
||||||
for (const auto &l = customLanguageModels().models.items(); const auto &i : l) {
|
for (const auto &l = customLanguageModels().models.items(); const auto &i : l) {
|
||||||
if (auto &m = CustomLanguageModel::fromBaseAspect(*i); m.name() == model)
|
if (auto &m = CustomLanguageModel::fromBaseAspect(*i); m.name() == model)
|
||||||
return m.commandLine(prompt);
|
return m.commandLine();
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@@ -9,8 +9,7 @@
|
|||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
CORE_EXPORT QStringList availableLanguageModels();
|
CORE_EXPORT QStringList availableLanguageModels();
|
||||||
CORE_EXPORT Utils::CommandLine commandLineForLanguageModel(
|
CORE_EXPORT Utils::CommandLine commandLineForLanguageModel(const QString &model);
|
||||||
const QString &model, const QString &prompt);
|
|
||||||
|
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
|
@@ -8,13 +8,19 @@
|
|||||||
#include "taskhub.h"
|
#include "taskhub.h"
|
||||||
|
|
||||||
#include <coreplugin/coreconstants.h>
|
#include <coreplugin/coreconstants.h>
|
||||||
|
#include <coreplugin/customlanguagemodels.h>
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
#include <coreplugin/iversioncontrol.h>
|
#include <coreplugin/iversioncontrol.h>
|
||||||
|
#include <coreplugin/messagemanager.h>
|
||||||
#include <coreplugin/vcsmanager.h>
|
#include <coreplugin/vcsmanager.h>
|
||||||
|
#include <utils/qtcprocess.h>
|
||||||
#include <utils/stringutils.h>
|
#include <utils/stringutils.h>
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
using namespace Core;
|
using namespace Core;
|
||||||
|
using namespace Utils;
|
||||||
|
|
||||||
namespace ProjectExplorer::Internal {
|
namespace ProjectExplorer::Internal {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -22,7 +28,7 @@ namespace {
|
|||||||
class ConfigTaskHandler : public ITaskHandler
|
class ConfigTaskHandler : public ITaskHandler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ConfigTaskHandler(const Task &pattern, Utils::Id page)
|
ConfigTaskHandler(const Task &pattern, Id page)
|
||||||
: m_pattern(pattern)
|
: m_pattern(pattern)
|
||||||
, m_targetPage(page)
|
, m_targetPage(page)
|
||||||
{}
|
{}
|
||||||
@@ -48,7 +54,7 @@ private:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
const Task m_pattern;
|
const Task m_pattern;
|
||||||
const Utils::Id m_targetPage;
|
const Id m_targetPage;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CopyTaskHandler : public ITaskHandler
|
class CopyTaskHandler : public ITaskHandler
|
||||||
@@ -77,10 +83,10 @@ private:
|
|||||||
lines << task.file.toUserOutput() + ':' + QString::number(task.line)
|
lines << task.file.toUserOutput() + ':' + QString::number(task.line)
|
||||||
+ ": " + type + task.description();
|
+ ": " + type + task.description();
|
||||||
}
|
}
|
||||||
Utils::setClipboardAndSelection(lines.join('\n'));
|
setClipboardAndSelection(lines.join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::Id actionManagerId() const override { return Utils::Id(Core::Constants::COPY); }
|
Id actionManagerId() const override { return Id(Core::Constants::COPY); }
|
||||||
QAction *createAction(QObject *parent) const override { return new QAction(parent); }
|
QAction *createAction(QObject *parent) const override { return new QAction(parent); }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -165,6 +171,72 @@ class VcsAnnotateTaskHandler : public ITaskHandler
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FIXME: There should be one handler per LLM, but the ITaskHandler infrastructure is
|
||||||
|
// currently static. Alternatively, we could somehow multiplex from here, perhaps via a submenu.
|
||||||
|
class ExplainWithAiHandler : public ITaskHandler
|
||||||
|
{
|
||||||
|
bool canHandle(const Task &task) const override
|
||||||
|
{
|
||||||
|
Q_UNUSED(task)
|
||||||
|
return !availableLanguageModels().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle(const Task &task) override
|
||||||
|
{
|
||||||
|
const QStringList llms = availableLanguageModels();
|
||||||
|
QTC_ASSERT(!llms.isEmpty(), return);
|
||||||
|
|
||||||
|
QString prompt;
|
||||||
|
if (task.origin.isEmpty())
|
||||||
|
prompt += "A software tool has emitted a diagnostic. ";
|
||||||
|
else
|
||||||
|
prompt += QString("The tool \"%1\" has emitted a diagnostic. ").arg(task.origin);
|
||||||
|
prompt += "Please explain what it is about. "
|
||||||
|
"Be as concise and concrete as possible and try to name the root cause."
|
||||||
|
"If you don't know the answer, just say so."
|
||||||
|
"If possible, also provide a solution. "
|
||||||
|
"Do not think for more than a minute. "
|
||||||
|
"Here is the error: ###%1##";
|
||||||
|
prompt = prompt.arg(task.description());
|
||||||
|
if (task.file.exists()) {
|
||||||
|
if (const auto contents = task.file.fileContents()) {
|
||||||
|
prompt.append('\n').append(
|
||||||
|
"Ideally, provide your solution in the form of a diff."
|
||||||
|
"Here are the contents of the file that the tool complained about: ###%1###."
|
||||||
|
"The path to the file is ###%2###.");
|
||||||
|
prompt = prompt.arg(QString::fromUtf8(*contents), task.file.toUserOutput());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto process = new Process;
|
||||||
|
process->setCommand(commandLineForLanguageModel(llms.first()));
|
||||||
|
process->setProcessMode(ProcessMode::Writer);
|
||||||
|
process->setTextChannelMode(Channel::Output, TextChannelMode::MultiLine);
|
||||||
|
process->setTextChannelMode(Channel::Error, TextChannelMode::MultiLine);
|
||||||
|
connect(process, &Process::textOnStandardOutput,
|
||||||
|
[](const QString &text) { MessageManager::writeFlashing(text); });
|
||||||
|
connect(process, &Process::textOnStandardError,
|
||||||
|
[](const QString &text) { MessageManager::writeFlashing(text); });
|
||||||
|
connect(process, &Process::done, [process] {
|
||||||
|
MessageManager::writeSilently(process->exitMessage());
|
||||||
|
process->deleteLater();
|
||||||
|
});
|
||||||
|
connect(process, &Process::started, [process, prompt] {
|
||||||
|
process->write(prompt);
|
||||||
|
process->closeWriteChannel();
|
||||||
|
});
|
||||||
|
QTimer::singleShot(60000, process, [process] { process->kill(); });
|
||||||
|
MessageManager::writeDisrupting(Tr::tr("Querying LLM..."));
|
||||||
|
process->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction *createAction(QObject *parent) const override
|
||||||
|
{
|
||||||
|
const auto action = new QAction(Tr::tr("Get help from AI"), parent);
|
||||||
|
action->setToolTip(Tr::tr("Ask an AI to help with this issue."));
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void setupTaskHandlers()
|
void setupTaskHandlers()
|
||||||
@@ -175,6 +247,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ProjectExplorer::Internal
|
} // namespace ProjectExplorer::Internal
|
||||||
|
Reference in New Issue
Block a user