#pragma once #include #include #include #include #include #include #include #include #include "chatstore.hpp" class QQmlEngine; class QJSEngine; class QNetworkReply; namespace ZShell { class Chat : public QObject { Q_OBJECT QML_ELEMENT QML_SINGLETON Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) Q_PROPERTY(QString endpoint READ endpoint NOTIFY endpointChanged) 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(QString lastError READ lastError NOTIFY lastErrorChanged) Q_PROPERTY(ChatStore* chats READ chats CONSTANT) Q_PROPERTY(QString streamingChatId READ streamingChatId NOTIFY streamingChatIdChanged) public: explicit Chat(QObject* parent = nullptr); ~Chat(); [[nodiscard]] bool busy() const { return m_busy; } [[nodiscard]] QString endpoint() const { return m_endpoint; } [[nodiscard]] QString model() const { return m_model; } [[nodiscard]] QStringList availableModels() const { return m_availableModels; } [[nodiscard]] int contextSize() const { return m_contextSize; } [[nodiscard]] int lastTokenCount() const { return m_lastTokenCount; } [[nodiscard]] QString lastError() const { return m_lastError; } [[nodiscard]] ChatStore* chats() const { return m_store; } [[nodiscard]] QString streamingChatId() const { return m_streamingChatId; } [[nodiscard]] ChatSession* streamingSession() const { return m_active; } Q_INVOKABLE void send(const QString& chatId, const QString& content); Q_INVOKABLE void stop(); Q_INVOKABLE void clearConversation(const QString& chatId); Q_INVOKABLE void dismissError(); Q_INVOKABLE void refreshModels(); Q_INVOKABLE void selectModel(const QString& id); static Chat* create(QQmlEngine*, QJSEngine*); Q_SIGNALS: void busyChanged(); void endpointChanged(); void modelChanged(); void availableModelsChanged(); void contextSizeChanged(); void errorOccurred(const QString& message); void lastErrorChanged(); void streamingChatIdChanged(); private: void beginAssistant(); void endStream(); void finalize(); void fail(const QString& message); void handleLine(const QByteArray& line); void drainBuffer(); void probeContextSize(); void setContextSize(int size); void updateTokenUsage(const QJsonObject& data); static void trimHistory(ChatSession* session, int contextSize); void shortRequest( const QString& tag, const QString& systemPrompt, const QString& userText, std::function onResult); void requestTitle(const QString& chatId, const QString& userText); void requestIcon(const QString& chatId, const QString& userText); static QString titleFrom(const QString& content); static QString completionsPath(const QString& endpoint, const QString& subpath); static QString serverErrorMessage( const QByteArray& body, const QString& fallback); static void setBusy(Chat* chat, bool value); static void setStreamingChatId(Chat* chat, const QString& id); QNetworkAccessManager m_manager; ChatStore* m_store = nullptr; QNetworkReply* m_reply = nullptr; QByteArray m_buffer; ChatSession* m_active = nullptr; ChatMessage* m_streaming = nullptr; QString m_pendingClearChatId; bool m_busy = false; QString m_endpoint; QString m_model; QStringList m_availableModels; QString m_lastError; QString m_streamingChatId; double m_temperature = 0.7; int m_contextSize = 0; int m_lastTokenCount = 0; static Chat* s_instance; friend class ChatStore; }; } // namespace ZShell