chore: restructure llm plugin to be more coherent & fix various bugs in llm ui
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 30s
Rust / build (pull_request) Successful in 49s
Python / verify (pull_request) Successful in 1m35s
Rust / fmt (pull_request) Successful in 36s
Rust / clippy (pull_request) Successful in 54s

This commit is contained in:
2026-10-18 12:58:38 +02:00
parent fc81be922f
commit 1fe8a3a959
54 changed files with 3022 additions and 2559 deletions
+53 -73
View File
@@ -1,18 +1,19 @@
#pragma once
#include "segment.hpp"
#include "chatstore.hpp"
#include "tool.hpp"
#include "turn.hpp"
#include <QByteArray>
#include <QJsonArray>
#include <QNetworkAccessManager>
#include <QObject>
#include <QPointer>
#include <QString>
#include <QStringList>
#include <QTimer>
#include <functional>
#include <memory>
class QJsonObject;
class QNetworkReply;
@@ -21,80 +22,75 @@ namespace ZShell::llm {
class ChatGeneration;
class ChatSession;
class LlmSegment;
class LlmTool;
class LlmClient : public QObject {
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
Q_PROPERTY(QString model READ model NOTIFY modelChanged)
Q_PROPERTY(
QStringList availableModels READ availableModels NOTIFY
availableModelsChanged)
Q_PROPERTY(int contextSize READ contextSize NOTIFY contextSizeChanged)
Q_PROPERTY(
bool toolsEnabled READ toolsEnabled WRITE setToolsEnabled NOTIFY
toolsEnabledChanged)
Q_PROPERTY(
bool sandboxEnabled READ sandboxEnabled WRITE setSandboxEnabled NOTIFY
sandboxEnabledChanged)
Q_PROPERTY(QString serverError READ serverError NOTIFY serverErrorChanged)
Q_PROPERTY(ZShell::llm::ChatStore* chats READ chats CONSTANT)
public:
explicit LlmClient(QObject* parent = nullptr);
~LlmClient() override;
[[nodiscard]] QString endpoint() const { return m_endpoint; }
[[nodiscard]] ChatStore* chats() const { return m_store; }
[[nodiscard]] bool busy() const { return m_busy; }
[[nodiscard]] QString model() const { return m_model; }
[[nodiscard]] double temperature() const { return m_temperature; }
[[nodiscard]] QStringList availableModels() const {
return m_availableModels;
}
[[nodiscard]] int contextSize() const { return m_contextSize; }
[[nodiscard]] ToolRegistry* tools() const { return m_tools; }
[[nodiscard]] QString serverError() const { return m_serverError; }
void setEndpoint(const QString& value);
void setModel(const QString& value);
void setTemperature(double value);
void setContextSize(int size);
void probeContextSize(std::function<void()> done = {});
void refreshAllStates();
[[nodiscard]] bool busy() const { return m_busy; }
[[nodiscard]] int contextSize() const { return m_tools->contextSize(); }
[[nodiscard]] bool toolsEnabled() const { return m_tools->enabled(); }
void setToolsEnabled(bool value) { m_tools->setEnabled(value); }
[[nodiscard]] bool sandboxEnabled() const { return m_sandboxEnabled; }
[[nodiscard]] bool sandboxEnabled() const {
return m_tools->sandboxEnabled();
}
[[nodiscard]] QString serverError() const { return m_serverError; }
[[nodiscard]] ChatSession* streamingSession() const {
return m_turn ? m_turn->session() : nullptr;
}
void setToolsEnabled(bool value);
void setSandboxEnabled(bool value);
[[nodiscard]] QString activeWorkspace() const;
[[nodiscard]] QString streamingChatId() const { return m_streamingChatId; }
[[nodiscard]] ChatSession* streamingSession() const { return m_active; }
Q_INVOKABLE void selectModel(const QString& id);
Q_INVOKABLE void stop();
Q_INVOKABLE void refreshModels();
Q_INVOKABLE void refreshFromServer();
void startGeneration(ChatSession* session, ChatGeneration* target);
void stop();
void endStream();
void clearOnFinish(ChatSession* session);
void sessionRemoved(ChatSession* session);
void approveTools();
void denyTools();
void refreshModels();
void approveTools(ZShell::llm::ChatGeneration* generation);
void denyTools(ZShell::llm::ChatGeneration* generation);
void requestTitle(ChatSession* session, const QString& userText);
void requestIcon(ChatSession* session, const QString& userText);
Q_SIGNALS:
void busyChanged();
void endpointChanged();
void modelChanged();
void availableModelsChanged();
void contextSizeChanged();
void toolsEnabledChanged();
void streamingChatIdChanged();
void errorOccurred(const QString& message);
void sandboxEnabledChanged();
void serverErrorChanged();
void titleSuggested(ZShell::llm::ChatSession* session, const QString& title);
void iconSuggested(ZShell::llm::ChatSession* session, const QString& icon);
void generationFailed(
ZShell::llm::ChatSession* session, const QString& message);
private:
struct ToolCallBuilder {
QString id;
QString name;
QString arguments;
QPointer<LlmSegment> segment;
bool seen = false;
};
void sendRound();
QJsonArray buildContextMessages(
ChatSession* session, int stopBeforeRow, int extraReserveTokens) const;
void appendSandboxPreamble(QJsonArray& messages);
void applyToolCallDelta(const QJsonObject& call);
void roundFinished();
void executeAllCalls();
@@ -102,13 +98,17 @@ class LlmClient : public QObject {
void setApprovalPending(bool value);
void finishPendingCalls(const QString& resultText);
void finishTurn();
void endStream();
void fail(const QString& message);
void handleLine(const QByteArray& line);
void drainBuffer();
void updateTokenUsage(const QJsonObject& data);
void setBusy(bool value);
void setStreamingChatId(const QString& id);
void setServerError(const QString& value);
void setModel(const QString& value);
void setContextSize(int size);
void probeContextSize(std::function<void()> done = {});
void refreshAllStates();
void shortRequest(
const QString& tag,
const QString& systemPrompt,
@@ -119,42 +119,22 @@ class LlmClient : public QObject {
static QString jsonErrorMessage(const QByteArray& body);
static QString serverErrorMessage(
const QByteArray& body, const QString& fallback);
[[nodiscard]] ToolContext toolContext() const;
QNetworkAccessManager m_manager;
ChatStore* m_store = nullptr;
ToolRegistry* m_tools = nullptr;
std::unique_ptr<Turn> m_turn;
QNetworkReply* m_reply = nullptr;
QByteArray m_buffer;
QTimer m_refreshTimer;
QPointer<ChatSession> m_active;
QPointer<ChatGeneration> m_streaming;
QPointer<ChatSession> m_pendingClear;
bool m_busy = false;
QString m_streamingChatId;
QString m_endpoint;
QString m_model;
QStringList m_availableModels;
QString m_serverError;
double m_temperature = 0.7;
int m_contextSize = 0;
QJsonArray m_transcript;
QList<ToolCallBuilder> m_callBuilders;
struct ToolCallResult {
QString content;
bool success = false;
};
QList<ToolCallResult> m_callResults;
QString m_finishReason;
int m_round = 0;
qsizetype m_contentMark = 0;
qsizetype m_reasoningMark = 0;
bool m_sandboxEnabled = true;
bool m_roundDone = false;
bool m_streamDone = false;
bool m_toolPhase = false;
bool m_approvalPending = false;
bool m_contextRetryUsed = false;
int m_pendingCalls = 0;
bool m_busy = false;
bool m_configEnabled = true;
static constexpr int kMaxToolRounds = 12;
};