mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-03 17:34:12 +02:00
Live mode: an AI assistant acts on the open project while the user watches
A new setting, Configurer QElectroTech > Général > "Autoriser un assistant IA à agir sur le projet ouvert", off by default. Off, nothing changes: no channel is opened. On, every start shows a warning first -- Continuer, Pas pour cette session, or Désactiver -- and nothing can connect until the user answers Continuer. It waits for any other start-up question to be answered, so the two never stack. Accepted, LiveServer opens a local socket only the user's account can use, with a random name and token written to live-session.json in the data folder for the qet MCP server, and removed when the channel closes. One JSON request per line: status (project, folio on screen, selection, last undo step, stored scripts), run_script and run_stored. Requests are queued out of the socket handler before they run (the lesson of PR #861). Each run is one undo step named "Assistant : <name>". The status bar shows the mode and the assistant's last action with a ✓ or ✗ and the time, and an Arrêter button that closes the channel for the session. Unticking the setting closes it at once. QetScripting::runSource() runs script text and, for a live run, returns what qet.log() wrote, the error with its line and the undo step instead of showing boxes; qet.showMessage() is logged rather than opening a box nobody asked for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -115,6 +115,22 @@ namespace {
|
||||
|
||||
bool runOnProject(const QString &scriptPath, QETProject *project, DiagramView *view,
|
||||
const QString &title)
|
||||
{
|
||||
QFile file(scriptPath);
|
||||
if (QetSettings::scriptingEnabled()
|
||||
&& !file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
err << "Cannot open script: " << scriptPath << "\n";
|
||||
return false;
|
||||
}
|
||||
const QString source = QString::fromUtf8(file.readAll());
|
||||
file.close();
|
||||
return runSource(source, scriptPath,
|
||||
title.isEmpty() ? QFileInfo(scriptPath).completeBaseName() : title,
|
||||
project, view, nullptr);
|
||||
}
|
||||
|
||||
bool runSource(const QString &source, const QString &fileName, const QString &title,
|
||||
QETProject *project, DiagramView *view, LiveRun *live)
|
||||
{
|
||||
// Checked here as well as at each caller, deliberately: this is the
|
||||
// one function that actually evaluates JavaScript, so it is the one
|
||||
@@ -123,23 +139,18 @@ bool runOnProject(const QString &scriptPath, QETProject *project, DiagramView *v
|
||||
// on the command line, an offer to switch the setting on in the editor.
|
||||
if (!QetSettings::scriptingEnabled()) {
|
||||
err << refusalMessage() << "\n";
|
||||
if (view) {
|
||||
if (live) {
|
||||
live->error = refusalMessage();
|
||||
} else if (view) {
|
||||
QET::QetMessageBox::warning(nullptr, QObject::tr("Script"),
|
||||
refusalMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile file(scriptPath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
err << "Cannot open script: " << scriptPath << "\n";
|
||||
return false;
|
||||
}
|
||||
const QString source = QString::fromUtf8(file.readAll());
|
||||
file.close();
|
||||
|
||||
QJSEngine engine;
|
||||
auto *api = new QetScriptApi(project, view, &engine);
|
||||
if (live) api->setLive(&live->log);
|
||||
QJSValue qet_value = engine.newQObject(api);
|
||||
// newQObject() takes ownership by default (QJSEngine::JavaScriptOwnership),
|
||||
// which would delete api as soon as the engine's GC decides to -- api's
|
||||
@@ -172,14 +183,14 @@ bool runOnProject(const QString &scriptPath, QETProject *project, DiagramView *v
|
||||
// has its macro taken off again: an obsolete command is deleted by
|
||||
// QUndoStack::undo() instead of being undone onto the redo side.
|
||||
QUndoStack *stack = (view && project) ? project->undoStack() : nullptr;
|
||||
const QString undo_text = live ? QObject::tr("Assistant : %1").arg(title)
|
||||
: QObject::tr("Script : %1").arg(title);
|
||||
if (stack) {
|
||||
stack->beginMacro(QObject::tr("Script : %1")
|
||||
.arg(title.isEmpty() ? QFileInfo(scriptPath).completeBaseName()
|
||||
: title));
|
||||
stack->beginMacro(undo_text);
|
||||
api->setUndoGrouped(true);
|
||||
}
|
||||
|
||||
QJSValue result = engine.evaluate(source, scriptPath);
|
||||
QJSValue result = engine.evaluate(source, fileName);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
finished = true;
|
||||
@@ -196,12 +207,14 @@ bool runOnProject(const QString &scriptPath, QETProject *project, DiagramView *v
|
||||
if (macro && macro->childCount() == 0) {
|
||||
const_cast<QUndoCommand *>(macro)->setObsolete(true);
|
||||
stack->undo();
|
||||
} else if (live) {
|
||||
live->undoText = undo_text;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.isError()) {
|
||||
const QString message = QStringLiteral("Script error: %1:%2: %3")
|
||||
.arg(scriptPath)
|
||||
.arg(fileName)
|
||||
.arg(result.property(QStringLiteral("lineNumber")).toInt())
|
||||
.arg(result.toString());
|
||||
err << message << "\n";
|
||||
@@ -209,7 +222,11 @@ bool runOnProject(const QString &scriptPath, QETProject *project, DiagramView *v
|
||||
// launched the GUI normally (nowhere on Windows, easy to miss
|
||||
// everywhere else). Headless --run has no GUI to show this in, and
|
||||
// no session for it to block.
|
||||
if (view) {
|
||||
if (live) {
|
||||
// The assistant gets the error and the user sees it in the
|
||||
// log: a box here would wait on someone who did not ask.
|
||||
live->error = message;
|
||||
} else if (view) {
|
||||
QET::QetMessageBox::critical(nullptr, QObject::tr("Script"), message);
|
||||
}
|
||||
return false;
|
||||
@@ -231,6 +248,13 @@ int run(const QStringList &)
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool runSource(const QString &, const QString &, const QString &, QETProject *,
|
||||
DiagramView *, LiveRun *live)
|
||||
{
|
||||
if (live) live->error = QStringLiteral("built without Qt Qml: no scripting");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool runOnProject(const QString &, QETProject *, DiagramView *, const QString &)
|
||||
{
|
||||
err << "This build of QElectroTech was compiled without the Qt Qml "
|
||||
|
||||
Reference in New Issue
Block a user