#pragma once #include #include #include #include #include #include #include class QJsonObject; class QNetworkReply; namespace ZShell::llm { class ChatGeneration; class ChatSession; // The only component that talks to the LLM server: owns the network // manager, the in-flight streaming state and the SSE parsing. class LlmClient : public QObject { Q_OBJECT public: explicit LlmClient(QObject* parent = nullptr); ~LlmClient() override; [[nodiscard]] QString endpoint() const { return m_endpoint; } [[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; } void setEndpoint(const QString& value); void setModel(const QString& value); void setTemperature(double value); void setContextSize(int size); void probeContextSize(); [[nodiscard]] bool busy() const { return m_busy; } [[nodiscard]] QString streamingChatId() const { return m_streamingChatId; } [[nodiscard]] ChatSession* streamingSession() const { return m_active; } // Streams a new assistant reply into `target` (the active generation of // a message of `session`). The context sent to the model is every // message of the session that is older than the target message. void startGeneration(ChatSession* session, ChatGeneration* target); void stop(); void endStream(); // Clears the session's conversation once the current stream ends. void clearOnFinish(ChatSession* session); // A session is about to be destroyed; drop any state pointing at it. void sessionRemoved(ChatSession* session); void refreshModels(); 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 streamingChatIdChanged(); void errorOccurred(const QString& message); void titleSuggested(ZShell::llm::ChatSession* session, const QString& title); void iconSuggested(ZShell::llm::ChatSession* session, const QString& icon); private: void finalize(); 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 shortRequest( const QString& tag, const QString& systemPrompt, const QString& userText, std::function onResult); static QString completionsPath(const QString& endpoint, const QString& subpath); static QString serverErrorMessage( const QByteArray& body, const QString& fallback); QNetworkAccessManager m_manager; QNetworkReply* m_reply = nullptr; QByteArray m_buffer; ChatSession* m_active = nullptr; ChatGeneration* m_streaming = nullptr; QPointer m_pendingClear; bool m_busy = false; QString m_streamingChatId; QString m_endpoint; QString m_model; QStringList m_availableModels; double m_temperature = 0.7; int m_contextSize = 0; }; } // namespace ZShell::llm