#include "chat.hpp" #include "config.hpp" #include "llm.hpp" #include "llmclient.hpp" #include "webfetchtool.hpp" #include namespace ZShell::llm { Chat::Chat(QObject* parent) : QObject(parent) , m_store(new ChatStore(this)) , m_client(new LlmClient(this)) { if (!config::Config::instance()) new config::Config(); m_store->setLlmClient(m_client); m_client->tools()->registerTool(new WebFetchTool(m_client->tools())); const auto* llm = config::Config::instance()->llm(); m_client->setEndpoint(llm->endpoint()); m_client->setModel(llm->model()); m_client->setTemperature(llm->temperature()); m_client->setToolsEnabled(llm->tools()); connect(llm, &config::Llm::endpointChanged, this, [this, llm]() { m_client->setEndpoint(llm->endpoint()); }); connect(llm, &config::Llm::modelChanged, this, [this, llm]() { m_client->setModel(llm->model()); }); connect(llm, &config::Llm::temperatureChanged, this, [this, llm]() { m_client->setTemperature(llm->temperature()); }); connect(llm, &config::Llm::toolsChanged, this, [this, llm]() { m_client->setToolsEnabled(llm->tools()); }); connect(m_client, &LlmClient::busyChanged, this, [this]() { // A fresh run supersedes the previous error. if (m_client->busy() && !m_lastError.isEmpty()) { m_lastError.clear(); Q_EMIT lastErrorChanged(); } Q_EMIT busyChanged(); }); connect(m_client, &LlmClient::endpointChanged, this, &Chat::endpointChanged); connect(m_client, &LlmClient::modelChanged, this, &Chat::modelChanged); connect( m_client, &LlmClient::availableModelsChanged, this, &Chat::availableModelsChanged); connect( m_client, &LlmClient::contextSizeChanged, this, &Chat::contextSizeChanged); connect( m_client, &LlmClient::streamingChatIdChanged, this, &Chat::streamingChatIdChanged); connect( m_client, &LlmClient::toolsEnabledChanged, this, &Chat::toolsEnabledChanged); connect( m_client, &LlmClient::errorOccurred, this, [this](const QString& message) { m_lastError = message; Q_EMIT lastErrorChanged(); Q_EMIT errorOccurred(message); }); connect( m_store, &ChatStore::sessionRemoved, this, [this](ChatSession* session) { m_client->sessionRemoved(session); }); connect( m_client, &LlmClient::titleSuggested, this, [this](ChatSession* session, const QString& title) { qInfo() << "Chat: applying generated title" << session->id() << title << "(was" << session->title() << ")"; session->setTitle(title); m_store->saveMeta(session); }); connect( m_client, &LlmClient::iconSuggested, this, [this](ChatSession* session, const QString& icon) { qInfo() << "Chat: applying generated icon" << session->id() << icon << "(was" << session->icon() << ")"; session->setIcon(icon); m_store->saveMeta(session); }); } bool Chat::busy() const { return m_client->busy(); } QString Chat::endpoint() const { return m_client->endpoint(); } QString Chat::model() const { return m_client->model(); } QStringList Chat::availableModels() const { return m_client->availableModels(); } int Chat::contextSize() const { return m_client->contextSize(); } bool Chat::toolsEnabled() const { return m_client->toolsEnabled(); } void Chat::setToolsEnabled(bool value) { m_client->setToolsEnabled(value); if (auto* config = config::Config::instance()) config->llm()->set_tools(value); } QString Chat::streamingChatId() const { return m_client->streamingChatId(); } Chat* Chat::s_instance = nullptr; Chat* Chat::create(QQmlEngine*, QJSEngine*) { if (!s_instance) s_instance = new Chat(); return s_instance; } void Chat::stop() { m_client->stop(); } void Chat::dismissError() { if (m_lastError.isEmpty()) return; m_lastError.clear(); Q_EMIT lastErrorChanged(); } void Chat::refreshModels() { m_client->refreshModels(); } void Chat::selectModel(const QString& id) { if (id.isEmpty()) return; m_client->setModel(id); if (auto* config = config::Config::instance()) config->llm()->set_model(id); } } // namespace ZShell::llm