mirror of
https://github.com/nomic-ai/gpt4all.git
synced 2025-07-19 17:50:25 +00:00
improve mixpanel usage statistics (#2238)
Other changes: - Always display first start dialog if privacy options are unset (e.g. if the user closed GPT4All without selecting them) - LocalDocs scanQueue is now always deferred - Fix a potential crash in magic_match - LocalDocs indexing is now started after the first start dialog is dismissed so usage stats are included Signed-off-by: Jared Van Bortel <jared@nomic.ai>
This commit is contained in:
parent
4193533154
commit
c622921894
@ -785,7 +785,7 @@ const std::vector<LLModel::Token> &GPTJ::endTokens() const
|
|||||||
return fres;
|
return fres;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_arch_name(gguf_context *ctx_gguf) {
|
const char *get_arch_name(gguf_context *ctx_gguf) {
|
||||||
std::string arch_name;
|
std::string arch_name;
|
||||||
const int kid = gguf_find_key(ctx_gguf, "general.architecture");
|
const int kid = gguf_find_key(ctx_gguf, "general.architecture");
|
||||||
enum gguf_type ktype = gguf_get_kv_type(ctx_gguf, kid);
|
enum gguf_type ktype = gguf_get_kv_type(ctx_gguf, kid);
|
||||||
@ -814,21 +814,25 @@ DLL_EXPORT const char *get_build_variant() {
|
|||||||
return GGML_BUILD_VARIANT;
|
return GGML_BUILD_VARIANT;
|
||||||
}
|
}
|
||||||
|
|
||||||
DLL_EXPORT bool magic_match(const char * fname) {
|
DLL_EXPORT char *get_file_arch(const char *fname) {
|
||||||
struct ggml_context * ctx_meta = NULL;
|
struct ggml_context * ctx_meta = NULL;
|
||||||
struct gguf_init_params params = {
|
struct gguf_init_params params = {
|
||||||
/*.no_alloc = */ true,
|
/*.no_alloc = */ true,
|
||||||
/*.ctx = */ &ctx_meta,
|
/*.ctx = */ &ctx_meta,
|
||||||
};
|
};
|
||||||
gguf_context *ctx_gguf = gguf_init_from_file(fname, params);
|
gguf_context *ctx_gguf = gguf_init_from_file(fname, params);
|
||||||
if (!ctx_gguf)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
bool isValid = gguf_get_version(ctx_gguf) <= 3;
|
char *arch = nullptr;
|
||||||
isValid = isValid && get_arch_name(ctx_gguf) == "gptj";
|
if (ctx_gguf && gguf_get_version(ctx_gguf) <= 3) {
|
||||||
|
arch = strdup(get_arch_name(ctx_gguf));
|
||||||
|
}
|
||||||
|
|
||||||
gguf_free(ctx_gguf);
|
gguf_free(ctx_gguf);
|
||||||
return isValid;
|
return arch;
|
||||||
|
}
|
||||||
|
|
||||||
|
DLL_EXPORT bool is_arch_supported(const char *arch) {
|
||||||
|
return !strcmp(arch, "gptj");
|
||||||
}
|
}
|
||||||
|
|
||||||
DLL_EXPORT LLModel *construct() {
|
DLL_EXPORT LLModel *construct() {
|
||||||
|
@ -104,7 +104,7 @@ static int llama_sample_top_p_top_k(
|
|||||||
return llama_sample_token(ctx, &candidates_p);
|
return llama_sample_token(ctx, &candidates_p);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_arch_name(gguf_context *ctx_gguf) {
|
const char *get_arch_name(gguf_context *ctx_gguf) {
|
||||||
std::string arch_name;
|
std::string arch_name;
|
||||||
const int kid = gguf_find_key(ctx_gguf, "general.architecture");
|
const int kid = gguf_find_key(ctx_gguf, "general.architecture");
|
||||||
enum gguf_type ktype = gguf_get_kv_type(ctx_gguf, kid);
|
enum gguf_type ktype = gguf_get_kv_type(ctx_gguf, kid);
|
||||||
@ -961,25 +961,23 @@ DLL_EXPORT const char *get_build_variant() {
|
|||||||
return GGML_BUILD_VARIANT;
|
return GGML_BUILD_VARIANT;
|
||||||
}
|
}
|
||||||
|
|
||||||
DLL_EXPORT bool magic_match(const char *fname) {
|
DLL_EXPORT char *get_file_arch(const char *fname) {
|
||||||
auto * ctx = load_gguf(fname);
|
auto *ctx = load_gguf(fname);
|
||||||
std::string arch = get_arch_name(ctx);
|
char *arch = nullptr;
|
||||||
|
if (ctx) {
|
||||||
bool valid = true;
|
std::string archStr = get_arch_name(ctx);
|
||||||
|
if (is_embedding_arch(archStr) && gguf_find_key(ctx, (archStr + ".pooling_type").c_str()) < 0) {
|
||||||
if (std::find(KNOWN_ARCHES.begin(), KNOWN_ARCHES.end(), arch) == KNOWN_ARCHES.end()) {
|
// old bert.cpp embedding model
|
||||||
// not supported by this version of llama.cpp
|
} else {
|
||||||
if (arch != "gptj") { // we support this via another module
|
arch = strdup(archStr.c_str());
|
||||||
std::cerr << __func__ << ": unsupported model architecture: " << arch << "\n";
|
|
||||||
}
|
}
|
||||||
valid = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valid && is_embedding_arch(arch) && gguf_find_key(ctx, (arch + ".pooling_type").c_str()) < 0)
|
|
||||||
valid = false; // old pre-llama.cpp embedding model, e.g. all-MiniLM-L6-v2-f16.gguf
|
|
||||||
|
|
||||||
gguf_free(ctx);
|
gguf_free(ctx);
|
||||||
return valid;
|
return arch;
|
||||||
|
}
|
||||||
|
|
||||||
|
DLL_EXPORT bool is_arch_supported(const char *arch) {
|
||||||
|
return std::find(KNOWN_ARCHES.begin(), KNOWN_ARCHES.end(), std::string(arch)) < KNOWN_ARCHES.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
DLL_EXPORT LLModel *construct() {
|
DLL_EXPORT LLModel *construct() {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -49,14 +50,17 @@ LLModel::Implementation::Implementation(Dlhandle &&dlhandle_)
|
|||||||
auto get_build_variant = m_dlhandle->get<const char *()>("get_build_variant");
|
auto get_build_variant = m_dlhandle->get<const char *()>("get_build_variant");
|
||||||
assert(get_build_variant);
|
assert(get_build_variant);
|
||||||
m_buildVariant = get_build_variant();
|
m_buildVariant = get_build_variant();
|
||||||
m_magicMatch = m_dlhandle->get<bool(const char*)>("magic_match");
|
m_getFileArch = m_dlhandle->get<char *(const char *)>("get_file_arch");
|
||||||
assert(m_magicMatch);
|
assert(m_getFileArch);
|
||||||
|
m_isArchSupported = m_dlhandle->get<bool(const char *)>("is_arch_supported");
|
||||||
|
assert(m_isArchSupported);
|
||||||
m_construct = m_dlhandle->get<LLModel *()>("construct");
|
m_construct = m_dlhandle->get<LLModel *()>("construct");
|
||||||
assert(m_construct);
|
assert(m_construct);
|
||||||
}
|
}
|
||||||
|
|
||||||
LLModel::Implementation::Implementation(Implementation &&o)
|
LLModel::Implementation::Implementation(Implementation &&o)
|
||||||
: m_magicMatch(o.m_magicMatch)
|
: m_getFileArch(o.m_getFileArch)
|
||||||
|
, m_isArchSupported(o.m_isArchSupported)
|
||||||
, m_construct(o.m_construct)
|
, m_construct(o.m_construct)
|
||||||
, m_modelType(o.m_modelType)
|
, m_modelType(o.m_modelType)
|
||||||
, m_buildVariant(o.m_buildVariant)
|
, m_buildVariant(o.m_buildVariant)
|
||||||
@ -123,18 +127,26 @@ const std::vector<LLModel::Implementation> &LLModel::Implementation::implementat
|
|||||||
|
|
||||||
const LLModel::Implementation* LLModel::Implementation::implementation(const char *fname, const std::string& buildVariant) {
|
const LLModel::Implementation* LLModel::Implementation::implementation(const char *fname, const std::string& buildVariant) {
|
||||||
bool buildVariantMatched = false;
|
bool buildVariantMatched = false;
|
||||||
|
std::optional<std::string> archName;
|
||||||
for (const auto& i : implementationList()) {
|
for (const auto& i : implementationList()) {
|
||||||
if (buildVariant != i.m_buildVariant) continue;
|
if (buildVariant != i.m_buildVariant) continue;
|
||||||
buildVariantMatched = true;
|
buildVariantMatched = true;
|
||||||
|
|
||||||
if (!i.m_magicMatch(fname)) continue;
|
char *arch = i.m_getFileArch(fname);
|
||||||
return &i;
|
if (!arch) continue;
|
||||||
|
archName = arch;
|
||||||
|
|
||||||
|
bool archSupported = i.m_isArchSupported(arch);
|
||||||
|
free(arch);
|
||||||
|
if (archSupported) return &i;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!buildVariantMatched)
|
if (!buildVariantMatched)
|
||||||
throw std::runtime_error("Could not find any implementations for build variant: " + buildVariant);
|
throw MissingImplementationError("Could not find any implementations for build variant: " + buildVariant);
|
||||||
|
if (!archName)
|
||||||
|
throw UnsupportedModelError("Unsupported file format");
|
||||||
|
|
||||||
return nullptr; // unsupported model format
|
throw BadArchError(std::move(*archName));
|
||||||
}
|
}
|
||||||
|
|
||||||
LLModel *LLModel::Implementation::construct(const std::string &modelPath, std::string buildVariant, int n_ctx) {
|
LLModel *LLModel::Implementation::construct(const std::string &modelPath, std::string buildVariant, int n_ctx) {
|
||||||
@ -144,7 +156,11 @@ LLModel *LLModel::Implementation::construct(const std::string &modelPath, std::s
|
|||||||
#if defined(__APPLE__) && defined(__arm64__) // FIXME: See if metal works for intel macs
|
#if defined(__APPLE__) && defined(__arm64__) // FIXME: See if metal works for intel macs
|
||||||
if (buildVariant == "auto") {
|
if (buildVariant == "auto") {
|
||||||
size_t total_mem = getSystemTotalRAMInBytes();
|
size_t total_mem = getSystemTotalRAMInBytes();
|
||||||
impl = implementation(modelPath.c_str(), "metal");
|
try {
|
||||||
|
impl = implementation(modelPath.c_str(), "metal");
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
// fall back to CPU
|
||||||
|
}
|
||||||
if(impl) {
|
if(impl) {
|
||||||
LLModel* metalimpl = impl->m_construct();
|
LLModel* metalimpl = impl->m_construct();
|
||||||
metalimpl->m_implementation = impl;
|
metalimpl->m_implementation = impl;
|
||||||
@ -177,7 +193,6 @@ LLModel *LLModel::Implementation::construct(const std::string &modelPath, std::s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl = implementation(modelPath.c_str(), buildVariant);
|
impl = implementation(modelPath.c_str(), buildVariant);
|
||||||
if (!impl) return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct and return llmodel implementation
|
// Construct and return llmodel implementation
|
||||||
|
@ -17,6 +17,29 @@ class LLModel {
|
|||||||
public:
|
public:
|
||||||
using Token = int32_t;
|
using Token = int32_t;
|
||||||
|
|
||||||
|
class BadArchError: public std::runtime_error {
|
||||||
|
public:
|
||||||
|
BadArchError(std::string arch)
|
||||||
|
: runtime_error("Unsupported model architecture: " + arch)
|
||||||
|
, m_arch(std::move(arch))
|
||||||
|
{}
|
||||||
|
|
||||||
|
const std::string &arch() const noexcept { return m_arch; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_arch;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MissingImplementationError: public std::runtime_error {
|
||||||
|
public:
|
||||||
|
using std::runtime_error::runtime_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
class UnsupportedModelError: public std::runtime_error {
|
||||||
|
public:
|
||||||
|
using std::runtime_error::runtime_error;
|
||||||
|
};
|
||||||
|
|
||||||
struct GPUDevice {
|
struct GPUDevice {
|
||||||
int index;
|
int index;
|
||||||
int type;
|
int type;
|
||||||
@ -53,7 +76,8 @@ public:
|
|||||||
static const Implementation *implementation(const char *fname, const std::string &buildVariant);
|
static const Implementation *implementation(const char *fname, const std::string &buildVariant);
|
||||||
static LLModel *constructDefaultLlama();
|
static LLModel *constructDefaultLlama();
|
||||||
|
|
||||||
bool (*m_magicMatch)(const char *fname);
|
char *(*m_getFileArch)(const char *fname);
|
||||||
|
bool (*m_isArchSupported)(const char *arch);
|
||||||
LLModel *(*m_construct)();
|
LLModel *(*m_construct)();
|
||||||
|
|
||||||
std::string_view m_modelType;
|
std::string_view m_modelType;
|
||||||
|
@ -40,11 +40,6 @@ llmodel_model llmodel_model_create2(const char *model_path, const char *build_va
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!llModel) {
|
|
||||||
llmodel_set_error(error, "Model format not supported (no matching implementation found)");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto wrapper = new LLModelWrapper;
|
auto wrapper = new LLModelWrapper;
|
||||||
wrapper->llModel = llModel;
|
wrapper->llModel = llModel;
|
||||||
return wrapper;
|
return wrapper;
|
||||||
|
@ -179,7 +179,7 @@ void Chat::promptProcessing()
|
|||||||
emit responseStateChanged();
|
emit responseStateChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chat::responseStopped()
|
void Chat::responseStopped(qint64 promptResponseMs)
|
||||||
{
|
{
|
||||||
m_tokenSpeed = QString();
|
m_tokenSpeed = QString();
|
||||||
emit tokenSpeedChanged();
|
emit tokenSpeedChanged();
|
||||||
@ -228,8 +228,13 @@ void Chat::responseStopped()
|
|||||||
emit responseStateChanged();
|
emit responseStateChanged();
|
||||||
if (m_generatedName.isEmpty())
|
if (m_generatedName.isEmpty())
|
||||||
emit generateNameRequested();
|
emit generateNameRequested();
|
||||||
if (chatModel()->count() < 3)
|
|
||||||
Network::globalInstance()->sendChatStarted();
|
Network::globalInstance()->trackChatEvent("response_complete", {
|
||||||
|
{"first", m_firstResponse},
|
||||||
|
{"message_count", chatModel()->count()},
|
||||||
|
{"$duration", promptResponseMs / 1000.},
|
||||||
|
});
|
||||||
|
m_firstResponse = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelInfo Chat::modelInfo() const
|
ModelInfo Chat::modelInfo() const
|
||||||
@ -331,7 +336,7 @@ void Chat::generatedNameChanged(const QString &name)
|
|||||||
|
|
||||||
void Chat::handleRecalculating()
|
void Chat::handleRecalculating()
|
||||||
{
|
{
|
||||||
Network::globalInstance()->sendRecalculatingContext(m_chatModel->count());
|
Network::globalInstance()->trackChatEvent("recalc_context", { {"length", m_chatModel->count()} });
|
||||||
emit recalcChanged();
|
emit recalcChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ private Q_SLOTS:
|
|||||||
void handleResponseChanged(const QString &response);
|
void handleResponseChanged(const QString &response);
|
||||||
void handleModelLoadingPercentageChanged(float);
|
void handleModelLoadingPercentageChanged(float);
|
||||||
void promptProcessing();
|
void promptProcessing();
|
||||||
void responseStopped();
|
void responseStopped(qint64 promptResponseMs);
|
||||||
void generatedNameChanged(const QString &name);
|
void generatedNameChanged(const QString &name);
|
||||||
void handleRecalculating();
|
void handleRecalculating();
|
||||||
void handleModelLoadingError(const QString &error);
|
void handleModelLoadingError(const QString &error);
|
||||||
@ -175,6 +175,7 @@ private:
|
|||||||
bool m_shouldDeleteLater = false;
|
bool m_shouldDeleteLater = false;
|
||||||
float m_modelLoadingPercentage = 0.0f;
|
float m_modelLoadingPercentage = 0.0f;
|
||||||
LocalDocsCollectionsModel *m_collectionModel;
|
LocalDocsCollectionsModel *m_collectionModel;
|
||||||
|
bool m_firstResponse = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CHAT_H
|
#endif // CHAT_H
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
#include "mysettings.h"
|
#include "mysettings.h"
|
||||||
#include "../gpt4all-backend/llmodel.h"
|
#include "../gpt4all-backend/llmodel.h"
|
||||||
|
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
|
||||||
//#define DEBUG
|
//#define DEBUG
|
||||||
//#define DEBUG_MODEL_LOADING
|
//#define DEBUG_MODEL_LOADING
|
||||||
|
|
||||||
@ -74,8 +76,6 @@ ChatLLM::ChatLLM(Chat *parent, bool isServer)
|
|||||||
, m_restoreStateFromText(false)
|
, m_restoreStateFromText(false)
|
||||||
{
|
{
|
||||||
moveToThread(&m_llmThread);
|
moveToThread(&m_llmThread);
|
||||||
connect(this, &ChatLLM::sendStartup, Network::globalInstance(), &Network::sendStartup);
|
|
||||||
connect(this, &ChatLLM::sendModelLoaded, Network::globalInstance(), &Network::sendModelLoaded);
|
|
||||||
connect(this, &ChatLLM::shouldBeLoadedChanged, this, &ChatLLM::handleShouldBeLoadedChanged,
|
connect(this, &ChatLLM::shouldBeLoadedChanged, this, &ChatLLM::handleShouldBeLoadedChanged,
|
||||||
Qt::QueuedConnection); // explicitly queued
|
Qt::QueuedConnection); // explicitly queued
|
||||||
connect(this, &ChatLLM::shouldTrySwitchContextChanged, this, &ChatLLM::handleShouldTrySwitchContextChanged,
|
connect(this, &ChatLLM::shouldTrySwitchContextChanged, this, &ChatLLM::handleShouldTrySwitchContextChanged,
|
||||||
@ -278,6 +278,7 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
|
|||||||
m_llModelInfo.fileInfo = fileInfo;
|
m_llModelInfo.fileInfo = fileInfo;
|
||||||
|
|
||||||
if (fileInfo.exists()) {
|
if (fileInfo.exists()) {
|
||||||
|
QVariantMap modelLoadProps;
|
||||||
if (modelInfo.isOnline) {
|
if (modelInfo.isOnline) {
|
||||||
QString apiKey;
|
QString apiKey;
|
||||||
QString modelName;
|
QString modelName;
|
||||||
@ -298,6 +299,9 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
|
|||||||
model->setAPIKey(apiKey);
|
model->setAPIKey(apiKey);
|
||||||
m_llModelInfo.model = model;
|
m_llModelInfo.model = model;
|
||||||
} else {
|
} else {
|
||||||
|
QElapsedTimer modelLoadTimer;
|
||||||
|
modelLoadTimer.start();
|
||||||
|
|
||||||
auto n_ctx = MySettings::globalInstance()->modelContextLength(modelInfo);
|
auto n_ctx = MySettings::globalInstance()->modelContextLength(modelInfo);
|
||||||
m_ctx.n_ctx = n_ctx;
|
m_ctx.n_ctx = n_ctx;
|
||||||
auto ngl = MySettings::globalInstance()->modelGpuLayers(modelInfo);
|
auto ngl = MySettings::globalInstance()->modelGpuLayers(modelInfo);
|
||||||
@ -307,7 +311,21 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
|
|||||||
if (m_forceMetal)
|
if (m_forceMetal)
|
||||||
buildVariant = "metal";
|
buildVariant = "metal";
|
||||||
#endif
|
#endif
|
||||||
m_llModelInfo.model = LLModel::Implementation::construct(filePath.toStdString(), buildVariant, n_ctx);
|
QString constructError;
|
||||||
|
m_llModelInfo.model = nullptr;
|
||||||
|
try {
|
||||||
|
m_llModelInfo.model = LLModel::Implementation::construct(filePath.toStdString(), buildVariant, n_ctx);
|
||||||
|
} catch (const LLModel::MissingImplementationError &e) {
|
||||||
|
modelLoadProps.insert("error", "missing_model_impl");
|
||||||
|
constructError = e.what();
|
||||||
|
} catch (const LLModel::UnsupportedModelError &e) {
|
||||||
|
modelLoadProps.insert("error", "unsupported_model_file");
|
||||||
|
constructError = e.what();
|
||||||
|
} catch (const LLModel::BadArchError &e) {
|
||||||
|
constructError = e.what();
|
||||||
|
modelLoadProps.insert("error", "unsupported_model_arch");
|
||||||
|
modelLoadProps.insert("model_arch", QString::fromStdString(e.arch()));
|
||||||
|
}
|
||||||
|
|
||||||
if (m_llModelInfo.model) {
|
if (m_llModelInfo.model) {
|
||||||
if (m_llModelInfo.model->isModelBlacklisted(filePath.toStdString())) {
|
if (m_llModelInfo.model->isModelBlacklisted(filePath.toStdString())) {
|
||||||
@ -368,6 +386,7 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
|
|||||||
// llama_init_from_file returned nullptr
|
// llama_init_from_file returned nullptr
|
||||||
emit reportDevice("CPU");
|
emit reportDevice("CPU");
|
||||||
emit reportFallbackReason("<br>GPU loading failed (out of VRAM?)");
|
emit reportFallbackReason("<br>GPU loading failed (out of VRAM?)");
|
||||||
|
modelLoadProps.insert("cpu_fallback_reason", "gpu_load_failed");
|
||||||
success = m_llModelInfo.model->loadModel(filePath.toStdString(), n_ctx, 0);
|
success = m_llModelInfo.model->loadModel(filePath.toStdString(), n_ctx, 0);
|
||||||
} else if (!m_llModelInfo.model->usingGPUDevice()) {
|
} else if (!m_llModelInfo.model->usingGPUDevice()) {
|
||||||
// ggml_vk_init was not called in llama.cpp
|
// ggml_vk_init was not called in llama.cpp
|
||||||
@ -375,6 +394,7 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
|
|||||||
// for instance if the quantization method is not supported on Vulkan yet
|
// for instance if the quantization method is not supported on Vulkan yet
|
||||||
emit reportDevice("CPU");
|
emit reportDevice("CPU");
|
||||||
emit reportFallbackReason("<br>model or quant has no GPU support");
|
emit reportFallbackReason("<br>model or quant has no GPU support");
|
||||||
|
modelLoadProps.insert("cpu_fallback_reason", "gpu_unsupported_model");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
@ -384,6 +404,7 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
|
|||||||
LLModelStore::globalInstance()->releaseModel(m_llModelInfo); // release back into the store
|
LLModelStore::globalInstance()->releaseModel(m_llModelInfo); // release back into the store
|
||||||
m_llModelInfo = LLModelInfo();
|
m_llModelInfo = LLModelInfo();
|
||||||
emit modelLoadingError(QString("Could not load model due to invalid model file for %1").arg(modelInfo.filename()));
|
emit modelLoadingError(QString("Could not load model due to invalid model file for %1").arg(modelInfo.filename()));
|
||||||
|
modelLoadProps.insert("error", "loadmodel_failed");
|
||||||
} else {
|
} else {
|
||||||
switch (m_llModelInfo.model->implementation().modelType()[0]) {
|
switch (m_llModelInfo.model->implementation().modelType()[0]) {
|
||||||
case 'L': m_llModelType = LLModelType::LLAMA_; break;
|
case 'L': m_llModelType = LLModelType::LLAMA_; break;
|
||||||
@ -398,12 +419,14 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
|
|||||||
emit modelLoadingError(QString("Could not determine model type for %1").arg(modelInfo.filename()));
|
emit modelLoadingError(QString("Could not determine model type for %1").arg(modelInfo.filename()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modelLoadProps.insert("$duration", modelLoadTimer.elapsed() / 1000.);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!m_isServer)
|
if (!m_isServer)
|
||||||
LLModelStore::globalInstance()->releaseModel(m_llModelInfo); // release back into the store
|
LLModelStore::globalInstance()->releaseModel(m_llModelInfo); // release back into the store
|
||||||
m_llModelInfo = LLModelInfo();
|
m_llModelInfo = LLModelInfo();
|
||||||
emit modelLoadingError(QString("Could not load model due to invalid format for %1").arg(modelInfo.filename()));
|
emit modelLoadingError(QString("Error loading %1: %2").arg(modelInfo.filename()).arg(constructError));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if defined(DEBUG_MODEL_LOADING)
|
#if defined(DEBUG_MODEL_LOADING)
|
||||||
@ -416,12 +439,9 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
|
|||||||
#endif
|
#endif
|
||||||
emit modelLoadingPercentageChanged(isModelLoaded() ? 1.0f : 0.0f);
|
emit modelLoadingPercentageChanged(isModelLoaded() ? 1.0f : 0.0f);
|
||||||
|
|
||||||
static bool isFirstLoad = true;
|
modelLoadProps.insert("requestedDevice", MySettings::globalInstance()->device());
|
||||||
if (isFirstLoad) {
|
modelLoadProps.insert("model", modelInfo.filename());
|
||||||
emit sendStartup();
|
Network::globalInstance()->trackChatEvent("model_load", modelLoadProps);
|
||||||
isFirstLoad = false;
|
|
||||||
} else
|
|
||||||
emit sendModelLoaded();
|
|
||||||
} else {
|
} else {
|
||||||
if (!m_isServer)
|
if (!m_isServer)
|
||||||
LLModelStore::globalInstance()->releaseModel(m_llModelInfo); // release back into the store
|
LLModelStore::globalInstance()->releaseModel(m_llModelInfo); // release back into the store
|
||||||
@ -632,6 +652,8 @@ bool ChatLLM::promptInternal(const QList<QString> &collectionList, const QString
|
|||||||
printf("%s", qPrintable(prompt));
|
printf("%s", qPrintable(prompt));
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
#endif
|
#endif
|
||||||
|
QElapsedTimer totalTime;
|
||||||
|
totalTime.start();
|
||||||
m_timer->start();
|
m_timer->start();
|
||||||
if (!docsContext.isEmpty()) {
|
if (!docsContext.isEmpty()) {
|
||||||
auto old_n_predict = std::exchange(m_ctx.n_predict, 0); // decode localdocs context without a response
|
auto old_n_predict = std::exchange(m_ctx.n_predict, 0); // decode localdocs context without a response
|
||||||
@ -644,12 +666,13 @@ bool ChatLLM::promptInternal(const QList<QString> &collectionList, const QString
|
|||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
#endif
|
#endif
|
||||||
m_timer->stop();
|
m_timer->stop();
|
||||||
|
qint64 elapsed = totalTime.elapsed();
|
||||||
std::string trimmed = trim_whitespace(m_response);
|
std::string trimmed = trim_whitespace(m_response);
|
||||||
if (trimmed != m_response) {
|
if (trimmed != m_response) {
|
||||||
m_response = trimmed;
|
m_response = trimmed;
|
||||||
emit responseChanged(QString::fromStdString(m_response));
|
emit responseChanged(QString::fromStdString(m_response));
|
||||||
}
|
}
|
||||||
emit responseStopped();
|
emit responseStopped(elapsed);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,9 +123,7 @@ Q_SIGNALS:
|
|||||||
void modelLoadingWarning(const QString &warning);
|
void modelLoadingWarning(const QString &warning);
|
||||||
void responseChanged(const QString &response);
|
void responseChanged(const QString &response);
|
||||||
void promptProcessing();
|
void promptProcessing();
|
||||||
void responseStopped();
|
void responseStopped(qint64 promptResponseMs);
|
||||||
void sendStartup();
|
|
||||||
void sendModelLoaded();
|
|
||||||
void generatedNameChanged(const QString &name);
|
void generatedNameChanged(const QString &name);
|
||||||
void stateChanged();
|
void stateChanged();
|
||||||
void threadStarted();
|
void threadStarted();
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "mysettings.h"
|
|
||||||
#include "embllm.h"
|
|
||||||
#include "embeddings.h"
|
#include "embeddings.h"
|
||||||
|
#include "embllm.h"
|
||||||
|
#include "mysettings.h"
|
||||||
|
#include "network.h"
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QPdfDocument>
|
#include <QPdfDocument>
|
||||||
@ -490,7 +492,7 @@ QSqlError initDb()
|
|||||||
i.collection = collection_name;
|
i.collection = collection_name;
|
||||||
i.folder_path = folder_path;
|
i.folder_path = folder_path;
|
||||||
i.folder_id = folder_id;
|
i.folder_id = folder_id;
|
||||||
emit addCollectionItem(i);
|
emit addCollectionItem(i, false);
|
||||||
|
|
||||||
// Add a document
|
// Add a document
|
||||||
int document_time = 123456789;
|
int document_time = 123456789;
|
||||||
@ -535,13 +537,13 @@ QSqlError initDb()
|
|||||||
|
|
||||||
Database::Database(int chunkSize)
|
Database::Database(int chunkSize)
|
||||||
: QObject(nullptr)
|
: QObject(nullptr)
|
||||||
, m_watcher(new QFileSystemWatcher(this))
|
|
||||||
, m_chunkSize(chunkSize)
|
, m_chunkSize(chunkSize)
|
||||||
|
, m_scanTimer(new QTimer(this))
|
||||||
|
, m_watcher(new QFileSystemWatcher(this))
|
||||||
, m_embLLM(new EmbeddingLLM)
|
, m_embLLM(new EmbeddingLLM)
|
||||||
, m_embeddings(new Embeddings(this))
|
, m_embeddings(new Embeddings(this))
|
||||||
{
|
{
|
||||||
moveToThread(&m_dbThread);
|
moveToThread(&m_dbThread);
|
||||||
connect(&m_dbThread, &QThread::started, this, &Database::start);
|
|
||||||
m_dbThread.setObjectName("database");
|
m_dbThread.setObjectName("database");
|
||||||
m_dbThread.start();
|
m_dbThread.start();
|
||||||
}
|
}
|
||||||
@ -556,11 +558,13 @@ void Database::scheduleNext(int folder_id, size_t countForFolder)
|
|||||||
{
|
{
|
||||||
emit updateCurrentDocsToIndex(folder_id, countForFolder);
|
emit updateCurrentDocsToIndex(folder_id, countForFolder);
|
||||||
if (!countForFolder) {
|
if (!countForFolder) {
|
||||||
emit updateIndexing(folder_id, false);
|
updateFolderStatus(folder_id, FolderStatus::Complete);
|
||||||
emit updateInstalled(folder_id, true);
|
emit updateInstalled(folder_id, true);
|
||||||
}
|
}
|
||||||
if (!m_docsToScan.isEmpty())
|
if (m_docsToScan.isEmpty()) {
|
||||||
QTimer::singleShot(0, this, &Database::scanQueue);
|
m_scanTimer->stop();
|
||||||
|
updateIndexingStatus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::handleDocumentError(const QString &errorMessage,
|
void Database::handleDocumentError(const QString &errorMessage,
|
||||||
@ -721,7 +725,6 @@ void Database::removeFolderFromDocumentQueue(int folder_id)
|
|||||||
return;
|
return;
|
||||||
m_docsToScan.remove(folder_id);
|
m_docsToScan.remove(folder_id);
|
||||||
emit removeFolderById(folder_id);
|
emit removeFolderById(folder_id);
|
||||||
emit docsToScanChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::enqueueDocumentInternal(const DocumentInfo &info, bool prepend)
|
void Database::enqueueDocumentInternal(const DocumentInfo &info, bool prepend)
|
||||||
@ -745,13 +748,16 @@ void Database::enqueueDocuments(int folder_id, const QVector<DocumentInfo> &info
|
|||||||
const size_t bytes = countOfBytes(folder_id);
|
const size_t bytes = countOfBytes(folder_id);
|
||||||
emit updateCurrentBytesToIndex(folder_id, bytes);
|
emit updateCurrentBytesToIndex(folder_id, bytes);
|
||||||
emit updateTotalBytesToIndex(folder_id, bytes);
|
emit updateTotalBytesToIndex(folder_id, bytes);
|
||||||
emit docsToScanChanged();
|
m_scanTimer->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::scanQueue()
|
void Database::scanQueue()
|
||||||
{
|
{
|
||||||
if (m_docsToScan.isEmpty())
|
if (m_docsToScan.isEmpty()) {
|
||||||
|
m_scanTimer->stop();
|
||||||
|
updateIndexingStatus();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DocumentInfo info = dequeueDocument();
|
DocumentInfo info = dequeueDocument();
|
||||||
const size_t countForFolder = countOfDocuments(info.folder);
|
const size_t countForFolder = countOfDocuments(info.folder);
|
||||||
@ -818,6 +824,8 @@ void Database::scanQueue()
|
|||||||
QSqlDatabase::database().transaction();
|
QSqlDatabase::database().transaction();
|
||||||
Q_ASSERT(document_id != -1);
|
Q_ASSERT(document_id != -1);
|
||||||
if (info.isPdf()) {
|
if (info.isPdf()) {
|
||||||
|
updateFolderStatus(folder_id, FolderStatus::Embedding, -1, info.currentPage == 0);
|
||||||
|
|
||||||
QPdfDocument doc;
|
QPdfDocument doc;
|
||||||
if (QPdfDocument::Error::None != doc.load(info.doc.canonicalFilePath())) {
|
if (QPdfDocument::Error::None != doc.load(info.doc.canonicalFilePath())) {
|
||||||
handleDocumentError("ERROR: Could not load pdf",
|
handleDocumentError("ERROR: Could not load pdf",
|
||||||
@ -850,6 +858,8 @@ void Database::scanQueue()
|
|||||||
emit subtractCurrentBytesToIndex(info.folder, bytes - (bytesPerPage * doc.pageCount()));
|
emit subtractCurrentBytesToIndex(info.folder, bytes - (bytesPerPage * doc.pageCount()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
updateFolderStatus(folder_id, FolderStatus::Embedding, -1, info.currentPosition == 0);
|
||||||
|
|
||||||
QFile file(document_path);
|
QFile file(document_path);
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
handleDocumentError("ERROR: Cannot open file for scanning",
|
handleDocumentError("ERROR: Cannot open file for scanning",
|
||||||
@ -884,7 +894,7 @@ void Database::scanQueue()
|
|||||||
return scheduleNext(folder_id, countForFolder);
|
return scheduleNext(folder_id, countForFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::scanDocuments(int folder_id, const QString &folder_path)
|
void Database::scanDocuments(int folder_id, const QString &folder_path, bool isNew)
|
||||||
{
|
{
|
||||||
#if defined(DEBUG)
|
#if defined(DEBUG)
|
||||||
qDebug() << "scanning folder for documents" << folder_path;
|
qDebug() << "scanning folder for documents" << folder_path;
|
||||||
@ -915,7 +925,7 @@ void Database::scanDocuments(int folder_id, const QString &folder_path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!infos.isEmpty()) {
|
if (!infos.isEmpty()) {
|
||||||
emit updateIndexing(folder_id, true);
|
updateFolderStatus(folder_id, FolderStatus::Started, infos.count(), false, isNew);
|
||||||
enqueueDocuments(folder_id, infos);
|
enqueueDocuments(folder_id, infos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -925,7 +935,7 @@ void Database::start()
|
|||||||
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &Database::directoryChanged);
|
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &Database::directoryChanged);
|
||||||
connect(m_embLLM, &EmbeddingLLM::embeddingsGenerated, this, &Database::handleEmbeddingsGenerated);
|
connect(m_embLLM, &EmbeddingLLM::embeddingsGenerated, this, &Database::handleEmbeddingsGenerated);
|
||||||
connect(m_embLLM, &EmbeddingLLM::errorGenerated, this, &Database::handleErrorGenerated);
|
connect(m_embLLM, &EmbeddingLLM::errorGenerated, this, &Database::handleErrorGenerated);
|
||||||
connect(this, &Database::docsToScanChanged, this, &Database::scanQueue);
|
m_scanTimer->callOnTimeout(this, &Database::scanQueue);
|
||||||
if (!QSqlDatabase::drivers().contains("QSQLITE")) {
|
if (!QSqlDatabase::drivers().contains("QSQLITE")) {
|
||||||
qWarning() << "ERROR: missing sqllite driver";
|
qWarning() << "ERROR: missing sqllite driver";
|
||||||
} else {
|
} else {
|
||||||
@ -937,10 +947,11 @@ void Database::start()
|
|||||||
if (m_embeddings->fileExists() && !m_embeddings->load())
|
if (m_embeddings->fileExists() && !m_embeddings->load())
|
||||||
qWarning() << "ERROR: Could not load embeddings";
|
qWarning() << "ERROR: Could not load embeddings";
|
||||||
|
|
||||||
addCurrentFolders();
|
int nAdded = addCurrentFolders();
|
||||||
|
Network::globalInstance()->trackEvent("localdocs_startup", { {"doc_collections_total", nAdded} });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::addCurrentFolders()
|
int Database::addCurrentFolders()
|
||||||
{
|
{
|
||||||
#if defined(DEBUG)
|
#if defined(DEBUG)
|
||||||
qDebug() << "addCurrentFolders";
|
qDebug() << "addCurrentFolders";
|
||||||
@ -950,21 +961,26 @@ void Database::addCurrentFolders()
|
|||||||
QList<CollectionItem> collections;
|
QList<CollectionItem> collections;
|
||||||
if (!selectAllFromCollections(q, &collections)) {
|
if (!selectAllFromCollections(q, &collections)) {
|
||||||
qWarning() << "ERROR: Cannot select collections" << q.lastError();
|
qWarning() << "ERROR: Cannot select collections" << q.lastError();
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit collectionListUpdated(collections);
|
emit collectionListUpdated(collections);
|
||||||
|
|
||||||
|
int nAdded = 0;
|
||||||
for (const auto &i : collections)
|
for (const auto &i : collections)
|
||||||
addFolder(i.collection, i.folder_path);
|
nAdded += addFolder(i.collection, i.folder_path, true);
|
||||||
|
|
||||||
|
updateIndexingStatus();
|
||||||
|
|
||||||
|
return nAdded;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::addFolder(const QString &collection, const QString &path)
|
bool Database::addFolder(const QString &collection, const QString &path, bool fromDb)
|
||||||
{
|
{
|
||||||
QFileInfo info(path);
|
QFileInfo info(path);
|
||||||
if (!info.exists() || !info.isReadable()) {
|
if (!info.exists() || !info.isReadable()) {
|
||||||
qWarning() << "ERROR: Cannot add folder that doesn't exist or not readable" << path;
|
qWarning() << "ERROR: Cannot add folder that doesn't exist or not readable" << path;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSqlQuery q;
|
QSqlQuery q;
|
||||||
@ -973,13 +989,13 @@ void Database::addFolder(const QString &collection, const QString &path)
|
|||||||
// See if the folder exists in the db
|
// See if the folder exists in the db
|
||||||
if (!selectFolder(q, path, &folder_id)) {
|
if (!selectFolder(q, path, &folder_id)) {
|
||||||
qWarning() << "ERROR: Cannot select folder from path" << path << q.lastError();
|
qWarning() << "ERROR: Cannot select folder from path" << path << q.lastError();
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the folder
|
// Add the folder
|
||||||
if (folder_id == -1 && !addFolderToDB(q, path, &folder_id)) {
|
if (folder_id == -1 && !addFolderToDB(q, path, &folder_id)) {
|
||||||
qWarning() << "ERROR: Cannot add folder to db with path" << path << q.lastError();
|
qWarning() << "ERROR: Cannot add folder to db with path" << path << q.lastError();
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(folder_id != -1);
|
Q_ASSERT(folder_id != -1);
|
||||||
@ -988,24 +1004,32 @@ void Database::addFolder(const QString &collection, const QString &path)
|
|||||||
QList<int> folders;
|
QList<int> folders;
|
||||||
if (!selectFoldersFromCollection(q, collection, &folders)) {
|
if (!selectFoldersFromCollection(q, collection, &folders)) {
|
||||||
qWarning() << "ERROR: Cannot select folders from collections" << collection << q.lastError();
|
qWarning() << "ERROR: Cannot select folders from collections" << collection << q.lastError();
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool added = false;
|
||||||
if (!folders.contains(folder_id)) {
|
if (!folders.contains(folder_id)) {
|
||||||
if (!addCollection(q, collection, folder_id)) {
|
if (!addCollection(q, collection, folder_id)) {
|
||||||
qWarning() << "ERROR: Cannot add folder to collection" << collection << path << q.lastError();
|
qWarning() << "ERROR: Cannot add folder to collection" << collection << path << q.lastError();
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionItem i;
|
CollectionItem i;
|
||||||
i.collection = collection;
|
i.collection = collection;
|
||||||
i.folder_path = path;
|
i.folder_path = path;
|
||||||
i.folder_id = folder_id;
|
i.folder_id = folder_id;
|
||||||
emit addCollectionItem(i);
|
emit addCollectionItem(i, fromDb);
|
||||||
|
added = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addFolderToWatch(path);
|
addFolderToWatch(path);
|
||||||
scanDocuments(folder_id, path);
|
scanDocuments(folder_id, path, !fromDb);
|
||||||
|
|
||||||
|
if (!fromDb) {
|
||||||
|
updateIndexingStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::removeFolder(const QString &collection, const QString &path)
|
void Database::removeFolder(const QString &collection, const QString &path)
|
||||||
@ -1285,5 +1309,69 @@ void Database::directoryChanged(const QString &path)
|
|||||||
cleanDB();
|
cleanDB();
|
||||||
|
|
||||||
// Rescan the documents associated with the folder
|
// Rescan the documents associated with the folder
|
||||||
scanDocuments(folder_id, path);
|
scanDocuments(folder_id, path, false);
|
||||||
|
updateIndexingStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Database::updateIndexingStatus() {
|
||||||
|
Q_ASSERT(m_scanTimer->isActive() || m_docsToScan.isEmpty());
|
||||||
|
if (!m_indexingTimer.isValid() && m_scanTimer->isActive()) {
|
||||||
|
Network::globalInstance()->trackEvent("localdocs_indexing_start");
|
||||||
|
m_indexingTimer.start();
|
||||||
|
} else if (m_indexingTimer.isValid() && !m_scanTimer->isActive()) {
|
||||||
|
qint64 durationMs = m_indexingTimer.elapsed();
|
||||||
|
Network::globalInstance()->trackEvent("localdocs_indexing_complete", { {"$duration", durationMs / 1000.} });
|
||||||
|
m_indexingTimer.invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Database::updateFolderStatus(int folder_id, Database::FolderStatus status, int numDocs, bool atStart, bool isNew) {
|
||||||
|
FolderStatusRecord *lastRecord = nullptr;
|
||||||
|
if (m_foldersBeingIndexed.contains(folder_id)) {
|
||||||
|
lastRecord = &m_foldersBeingIndexed[folder_id];
|
||||||
|
}
|
||||||
|
Q_ASSERT(lastRecord || status == FolderStatus::Started);
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case FolderStatus::Started:
|
||||||
|
if (lastRecord == nullptr) {
|
||||||
|
// record timestamp but don't send an event yet
|
||||||
|
m_foldersBeingIndexed.insert(folder_id, { QDateTime::currentMSecsSinceEpoch(), isNew, numDocs });
|
||||||
|
emit updateIndexing(folder_id, true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FolderStatus::Embedding:
|
||||||
|
if (!lastRecord->docsChanged) {
|
||||||
|
Q_ASSERT(atStart);
|
||||||
|
// send start event with the original timestamp for folders that need updating
|
||||||
|
const auto *embeddingModels = ModelList::globalInstance()->installedEmbeddingModels();
|
||||||
|
Network::globalInstance()->trackEvent("localdocs_folder_indexing", {
|
||||||
|
{"folder_id", folder_id},
|
||||||
|
{"is_new_collection", lastRecord->isNew},
|
||||||
|
{"document_count", lastRecord->numDocs},
|
||||||
|
{"embedding_model", embeddingModels->defaultModelInfo().filename()},
|
||||||
|
{"chunk_size", m_chunkSize},
|
||||||
|
{"time", lastRecord->startTime},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
lastRecord->docsChanged += atStart;
|
||||||
|
lastRecord->chunksRead++;
|
||||||
|
break;
|
||||||
|
case FolderStatus::Complete:
|
||||||
|
if (lastRecord->docsChanged) {
|
||||||
|
// send complete event for folders that were updated
|
||||||
|
qint64 durationMs = QDateTime::currentMSecsSinceEpoch() - lastRecord->startTime;
|
||||||
|
Network::globalInstance()->trackEvent("localdocs_folder_complete", {
|
||||||
|
{"folder_id", folder_id},
|
||||||
|
{"is_new_collection", lastRecord->isNew},
|
||||||
|
{"documents_total", lastRecord->numDocs},
|
||||||
|
{"documents_changed", lastRecord->docsChanged},
|
||||||
|
{"chunks_read", lastRecord->chunksRead},
|
||||||
|
{"$duration", durationMs / 1000.},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
m_foldersBeingIndexed.remove(folder_id);
|
||||||
|
emit updateIndexing(folder_id, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
#ifndef DATABASE_H
|
#ifndef DATABASE_H
|
||||||
#define DATABASE_H
|
#define DATABASE_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QElapsedTimer>
|
||||||
#include <QtSql>
|
|
||||||
#include <QQueue>
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QThread>
|
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQueue>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QtSql>
|
||||||
|
|
||||||
#include "embllm.h"
|
#include "embllm.h"
|
||||||
|
|
||||||
class Embeddings;
|
class Embeddings;
|
||||||
|
class QTimer;
|
||||||
|
|
||||||
struct DocumentInfo
|
struct DocumentInfo
|
||||||
{
|
{
|
||||||
int folder;
|
int folder;
|
||||||
@ -58,9 +61,10 @@ public:
|
|||||||
virtual ~Database();
|
virtual ~Database();
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
|
void start();
|
||||||
void scanQueue();
|
void scanQueue();
|
||||||
void scanDocuments(int folder_id, const QString &folder_path);
|
void scanDocuments(int folder_id, const QString &folder_path, bool isNew);
|
||||||
void addFolder(const QString &collection, const QString &path);
|
bool addFolder(const QString &collection, const QString &path, bool fromDb);
|
||||||
void removeFolder(const QString &collection, const QString &path);
|
void removeFolder(const QString &collection, const QString &path);
|
||||||
void retrieveFromDB(const QList<QString> &collections, const QString &text, int retrievalSize, QList<ResultInfo> *results);
|
void retrieveFromDB(const QList<QString> &collections, const QString &text, int retrievalSize, QList<ResultInfo> *results);
|
||||||
void cleanDB();
|
void cleanDB();
|
||||||
@ -78,21 +82,22 @@ Q_SIGNALS:
|
|||||||
void updateTotalBytesToIndex(int folder_id, size_t totalBytesToIndex);
|
void updateTotalBytesToIndex(int folder_id, size_t totalBytesToIndex);
|
||||||
void updateCurrentEmbeddingsToIndex(int folder_id, size_t currentBytesToIndex);
|
void updateCurrentEmbeddingsToIndex(int folder_id, size_t currentBytesToIndex);
|
||||||
void updateTotalEmbeddingsToIndex(int folder_id, size_t totalBytesToIndex);
|
void updateTotalEmbeddingsToIndex(int folder_id, size_t totalBytesToIndex);
|
||||||
void addCollectionItem(const CollectionItem &item);
|
void addCollectionItem(const CollectionItem &item, bool fromDb);
|
||||||
void removeFolderById(int folder_id);
|
void removeFolderById(int folder_id);
|
||||||
void removeCollectionItem(const QString &collectionName);
|
|
||||||
void collectionListUpdated(const QList<CollectionItem> &collectionList);
|
void collectionListUpdated(const QList<CollectionItem> &collectionList);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void start();
|
|
||||||
void directoryChanged(const QString &path);
|
void directoryChanged(const QString &path);
|
||||||
bool addFolderToWatch(const QString &path);
|
bool addFolderToWatch(const QString &path);
|
||||||
bool removeFolderFromWatch(const QString &path);
|
bool removeFolderFromWatch(const QString &path);
|
||||||
void addCurrentFolders();
|
int addCurrentFolders();
|
||||||
void handleEmbeddingsGenerated(const QVector<EmbeddingResult> &embeddings);
|
void handleEmbeddingsGenerated(const QVector<EmbeddingResult> &embeddings);
|
||||||
void handleErrorGenerated(int folder_id, const QString &error);
|
void handleErrorGenerated(int folder_id, const QString &error);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum class FolderStatus { Started, Embedding, Complete };
|
||||||
|
struct FolderStatusRecord { qint64 startTime; bool isNew; int numDocs, docsChanged, chunksRead; };
|
||||||
|
|
||||||
void removeFolderInternal(const QString &collection, int folder_id, const QString &path);
|
void removeFolderInternal(const QString &collection, int folder_id, const QString &path);
|
||||||
size_t chunkStream(QTextStream &stream, int folder_id, int document_id, const QString &file,
|
size_t chunkStream(QTextStream &stream, int folder_id, int document_id, const QString &file,
|
||||||
const QString &title, const QString &author, const QString &subject, const QString &keywords, int page,
|
const QString &title, const QString &author, const QString &subject, const QString &keywords, int page,
|
||||||
@ -107,10 +112,15 @@ private:
|
|||||||
void removeFolderFromDocumentQueue(int folder_id);
|
void removeFolderFromDocumentQueue(int folder_id);
|
||||||
void enqueueDocumentInternal(const DocumentInfo &info, bool prepend = false);
|
void enqueueDocumentInternal(const DocumentInfo &info, bool prepend = false);
|
||||||
void enqueueDocuments(int folder_id, const QVector<DocumentInfo> &infos);
|
void enqueueDocuments(int folder_id, const QVector<DocumentInfo> &infos);
|
||||||
|
void updateIndexingStatus();
|
||||||
|
void updateFolderStatus(int folder_id, FolderStatus status, int numDocs = -1, bool atStart = false, bool isNew = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_chunkSize;
|
int m_chunkSize;
|
||||||
|
QTimer *m_scanTimer;
|
||||||
QMap<int, QQueue<DocumentInfo>> m_docsToScan;
|
QMap<int, QQueue<DocumentInfo>> m_docsToScan;
|
||||||
|
QElapsedTimer m_indexingTimer;
|
||||||
|
QMap<int, FolderStatusRecord> m_foldersBeingIndexed;
|
||||||
QList<ResultInfo> m_retrieve;
|
QList<ResultInfo> m_retrieve;
|
||||||
QThread m_dbThread;
|
QThread m_dbThread;
|
||||||
QFileSystemWatcher *m_watcher;
|
QFileSystemWatcher *m_watcher;
|
||||||
|
@ -75,15 +75,25 @@ bool Download::hasNewerRelease() const
|
|||||||
return compareVersions(versions.first(), currentVersion);
|
return compareVersions(versions.first(), currentVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Download::isFirstStart() const
|
bool Download::isFirstStart(bool writeVersion) const
|
||||||
{
|
{
|
||||||
|
auto *mySettings = MySettings::globalInstance();
|
||||||
|
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
settings.sync();
|
settings.sync();
|
||||||
QString lastVersionStarted = settings.value("download/lastVersionStarted").toString();
|
QString lastVersionStarted = settings.value("download/lastVersionStarted").toString();
|
||||||
bool first = lastVersionStarted != QCoreApplication::applicationVersion();
|
bool first = lastVersionStarted != QCoreApplication::applicationVersion();
|
||||||
settings.setValue("download/lastVersionStarted", QCoreApplication::applicationVersion());
|
if (first && writeVersion) {
|
||||||
settings.sync();
|
settings.setValue("download/lastVersionStarted", QCoreApplication::applicationVersion());
|
||||||
return first;
|
// let the user select these again
|
||||||
|
settings.remove("network/usageStatsActive");
|
||||||
|
settings.remove("network/isActive");
|
||||||
|
settings.sync();
|
||||||
|
emit mySettings->networkUsageStatsActiveChanged();
|
||||||
|
emit mySettings->networkIsActiveChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
return first || !mySettings->isNetworkUsageStatsActiveSet() || !mySettings->isNetworkIsActiveSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Download::updateReleaseNotes()
|
void Download::updateReleaseNotes()
|
||||||
@ -131,7 +141,7 @@ void Download::downloadModel(const QString &modelFile)
|
|||||||
ModelList::globalInstance()->updateDataByFilename(modelFile, {{ ModelList::DownloadingRole, true }});
|
ModelList::globalInstance()->updateDataByFilename(modelFile, {{ ModelList::DownloadingRole, true }});
|
||||||
ModelInfo info = ModelList::globalInstance()->modelInfoByFilename(modelFile);
|
ModelInfo info = ModelList::globalInstance()->modelInfoByFilename(modelFile);
|
||||||
QString url = !info.url().isEmpty() ? info.url() : "http://gpt4all.io/models/gguf/" + modelFile;
|
QString url = !info.url().isEmpty() ? info.url() : "http://gpt4all.io/models/gguf/" + modelFile;
|
||||||
Network::globalInstance()->sendDownloadStarted(modelFile);
|
Network::globalInstance()->trackEvent("download_started", { {"model", modelFile} });
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
request.setAttribute(QNetworkRequest::User, modelFile);
|
request.setAttribute(QNetworkRequest::User, modelFile);
|
||||||
request.setRawHeader("range", QString("bytes=%1-").arg(tempFile->pos()).toUtf8());
|
request.setRawHeader("range", QString("bytes=%1-").arg(tempFile->pos()).toUtf8());
|
||||||
@ -153,7 +163,7 @@ void Download::cancelDownload(const QString &modelFile)
|
|||||||
QNetworkReply *modelReply = m_activeDownloads.keys().at(i);
|
QNetworkReply *modelReply = m_activeDownloads.keys().at(i);
|
||||||
QUrl url = modelReply->request().url();
|
QUrl url = modelReply->request().url();
|
||||||
if (url.toString().endsWith(modelFile)) {
|
if (url.toString().endsWith(modelFile)) {
|
||||||
Network::globalInstance()->sendDownloadCanceled(modelFile);
|
Network::globalInstance()->trackEvent("download_canceled", { {"model", modelFile} });
|
||||||
|
|
||||||
// Disconnect the signals
|
// Disconnect the signals
|
||||||
disconnect(modelReply, &QNetworkReply::downloadProgress, this, &Download::handleDownloadProgress);
|
disconnect(modelReply, &QNetworkReply::downloadProgress, this, &Download::handleDownloadProgress);
|
||||||
@ -178,7 +188,8 @@ void Download::installModel(const QString &modelFile, const QString &apiKey)
|
|||||||
if (apiKey.isEmpty())
|
if (apiKey.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Network::globalInstance()->sendInstallModel(modelFile);
|
Network::globalInstance()->trackEvent("install_model", { {"model", modelFile} });
|
||||||
|
|
||||||
QString filePath = MySettings::globalInstance()->modelPath() + modelFile;
|
QString filePath = MySettings::globalInstance()->modelPath() + modelFile;
|
||||||
QFile file(filePath);
|
QFile file(filePath);
|
||||||
if (file.open(QIODeviceBase::WriteOnly | QIODeviceBase::Text)) {
|
if (file.open(QIODeviceBase::WriteOnly | QIODeviceBase::Text)) {
|
||||||
@ -216,7 +227,7 @@ void Download::removeModel(const QString &modelFile)
|
|||||||
shouldRemoveInstalled = info.installed && !info.isClone() && (info.isDiscovered() || info.description() == "" /*indicates sideloaded*/);
|
shouldRemoveInstalled = info.installed && !info.isClone() && (info.isDiscovered() || info.description() == "" /*indicates sideloaded*/);
|
||||||
if (shouldRemoveInstalled)
|
if (shouldRemoveInstalled)
|
||||||
ModelList::globalInstance()->removeInstalled(info);
|
ModelList::globalInstance()->removeInstalled(info);
|
||||||
Network::globalInstance()->sendRemoveModel(modelFile);
|
Network::globalInstance()->trackEvent("remove_model", { {"model", modelFile} });
|
||||||
file.remove();
|
file.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,7 +343,11 @@ void Download::handleErrorOccurred(QNetworkReply::NetworkError code)
|
|||||||
.arg(modelReply->errorString());
|
.arg(modelReply->errorString());
|
||||||
qWarning() << error;
|
qWarning() << error;
|
||||||
ModelList::globalInstance()->updateDataByFilename(modelFilename, {{ ModelList::DownloadErrorRole, error }});
|
ModelList::globalInstance()->updateDataByFilename(modelFilename, {{ ModelList::DownloadErrorRole, error }});
|
||||||
Network::globalInstance()->sendDownloadError(modelFilename, (int)code, modelReply->errorString());
|
Network::globalInstance()->trackEvent("download_error", {
|
||||||
|
{"model", modelFilename},
|
||||||
|
{"code", (int)code},
|
||||||
|
{"error", modelReply->errorString()},
|
||||||
|
});
|
||||||
cancelDownload(modelFilename);
|
cancelDownload(modelFilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,7 +530,7 @@ void Download::handleHashAndSaveFinished(bool success, const QString &error,
|
|||||||
// The hash and save should send back with tempfile closed
|
// The hash and save should send back with tempfile closed
|
||||||
Q_ASSERT(!tempFile->isOpen());
|
Q_ASSERT(!tempFile->isOpen());
|
||||||
QString modelFilename = modelReply->request().attribute(QNetworkRequest::User).toString();
|
QString modelFilename = modelReply->request().attribute(QNetworkRequest::User).toString();
|
||||||
Network::globalInstance()->sendDownloadFinished(modelFilename, success);
|
Network::globalInstance()->trackEvent("download_finished", { {"model", modelFilename}, {"success", success} });
|
||||||
|
|
||||||
QVector<QPair<int, QVariant>> data {
|
QVector<QPair<int, QVariant>> data {
|
||||||
{ ModelList::CalcHashRole, false },
|
{ ModelList::CalcHashRole, false },
|
||||||
|
@ -54,7 +54,7 @@ public:
|
|||||||
Q_INVOKABLE void cancelDownload(const QString &modelFile);
|
Q_INVOKABLE void cancelDownload(const QString &modelFile);
|
||||||
Q_INVOKABLE void installModel(const QString &modelFile, const QString &apiKey);
|
Q_INVOKABLE void installModel(const QString &modelFile, const QString &apiKey);
|
||||||
Q_INVOKABLE void removeModel(const QString &modelFile);
|
Q_INVOKABLE void removeModel(const QString &modelFile);
|
||||||
Q_INVOKABLE bool isFirstStart() const;
|
Q_INVOKABLE bool isFirstStart(bool writeVersion = false) const;
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void updateReleaseNotes();
|
void updateReleaseNotes();
|
||||||
|
@ -57,7 +57,14 @@ bool EmbeddingLLMWorker::loadModel()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_model = LLModel::Implementation::construct(filePath.toStdString());
|
try {
|
||||||
|
m_model = LLModel::Implementation::construct(filePath.toStdString());
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
qWarning() << "WARNING: Could not load embedding model:" << e.what();
|
||||||
|
m_model = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: explicitly loads model on CPU to avoid GPU OOM
|
// NOTE: explicitly loads model on CPU to avoid GPU OOM
|
||||||
// TODO(cebtenzzre): support GPU-accelerated embeddings
|
// TODO(cebtenzzre): support GPU-accelerated embeddings
|
||||||
bool success = m_model->loadModel(filePath.toStdString(), 2048, 0);
|
bool success = m_model->loadModel(filePath.toStdString(), 2048, 0);
|
||||||
|
@ -49,7 +49,7 @@ bool LLM::checkForUpdates() const
|
|||||||
#pragma message "offline installer build will not check for updates!"
|
#pragma message "offline installer build will not check for updates!"
|
||||||
return QDesktopServices::openUrl(QUrl("https://gpt4all.io/"));
|
return QDesktopServices::openUrl(QUrl("https://gpt4all.io/"));
|
||||||
#else
|
#else
|
||||||
Network::globalInstance()->sendCheckForUpdates();
|
Network::globalInstance()->trackEvent("check_for_updates");
|
||||||
|
|
||||||
#if defined(Q_OS_LINUX)
|
#if defined(Q_OS_LINUX)
|
||||||
QString tool("maintenancetool");
|
QString tool("maintenancetool");
|
||||||
|
@ -18,6 +18,8 @@ LocalDocs::LocalDocs()
|
|||||||
// Create the DB with the chunk size from settings
|
// Create the DB with the chunk size from settings
|
||||||
m_database = new Database(MySettings::globalInstance()->localDocsChunkSize());
|
m_database = new Database(MySettings::globalInstance()->localDocsChunkSize());
|
||||||
|
|
||||||
|
connect(this, &LocalDocs::requestStart, m_database,
|
||||||
|
&Database::start, Qt::QueuedConnection);
|
||||||
connect(this, &LocalDocs::requestAddFolder, m_database,
|
connect(this, &LocalDocs::requestAddFolder, m_database,
|
||||||
&Database::addFolder, Qt::QueuedConnection);
|
&Database::addFolder, Qt::QueuedConnection);
|
||||||
connect(this, &LocalDocs::requestRemoveFolder, m_database,
|
connect(this, &LocalDocs::requestRemoveFolder, m_database,
|
||||||
@ -50,8 +52,6 @@ LocalDocs::LocalDocs()
|
|||||||
m_localDocsModel, &LocalDocsModel::addCollectionItem, Qt::QueuedConnection);
|
m_localDocsModel, &LocalDocsModel::addCollectionItem, Qt::QueuedConnection);
|
||||||
connect(m_database, &Database::removeFolderById,
|
connect(m_database, &Database::removeFolderById,
|
||||||
m_localDocsModel, &LocalDocsModel::removeFolderById, Qt::QueuedConnection);
|
m_localDocsModel, &LocalDocsModel::removeFolderById, Qt::QueuedConnection);
|
||||||
connect(m_database, &Database::removeCollectionItem,
|
|
||||||
m_localDocsModel, &LocalDocsModel::removeCollectionItem, Qt::QueuedConnection);
|
|
||||||
connect(m_database, &Database::collectionListUpdated,
|
connect(m_database, &Database::collectionListUpdated,
|
||||||
m_localDocsModel, &LocalDocsModel::collectionListUpdated, Qt::QueuedConnection);
|
m_localDocsModel, &LocalDocsModel::collectionListUpdated, Qt::QueuedConnection);
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ void LocalDocs::addFolder(const QString &collection, const QString &path)
|
|||||||
{
|
{
|
||||||
const QUrl url(path);
|
const QUrl url(path);
|
||||||
const QString localPath = url.isLocalFile() ? url.toLocalFile() : path;
|
const QString localPath = url.isLocalFile() ? url.toLocalFile() : path;
|
||||||
emit requestAddFolder(collection, localPath);
|
emit requestAddFolder(collection, localPath, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalDocs::removeFolder(const QString &collection, const QString &path)
|
void LocalDocs::removeFolder(const QString &collection, const QString &path)
|
||||||
|
@ -26,7 +26,8 @@ public Q_SLOTS:
|
|||||||
void aboutToQuit();
|
void aboutToQuit();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void requestAddFolder(const QString &collection, const QString &path);
|
void requestStart();
|
||||||
|
void requestAddFolder(const QString &collection, const QString &path, bool fromDb);
|
||||||
void requestRemoveFolder(const QString &collection, const QString &path);
|
void requestRemoveFolder(const QString &collection, const QString &path);
|
||||||
void requestChunkSizeChange(int chunkSize);
|
void requestChunkSizeChange(int chunkSize);
|
||||||
void localDocsModelChanged();
|
void localDocsModelChanged();
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
#ifndef LOCALDOCS_H
|
|
||||||
#define LOCALDOCS_H
|
|
||||||
|
|
||||||
#include "localdocsmodel.h"
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QtSql>
|
|
||||||
#include <QQueue>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QThread>
|
|
||||||
#include <QFileSystemWatcher>
|
|
||||||
|
|
||||||
struct DocumentInfo
|
|
||||||
{
|
|
||||||
int folder;
|
|
||||||
QFileInfo doc;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CollectionItem {
|
|
||||||
QString collection;
|
|
||||||
QString folder_path;
|
|
||||||
int folder_id = -1;
|
|
||||||
};
|
|
||||||
Q_DECLARE_METATYPE(CollectionItem)
|
|
||||||
|
|
||||||
class Database : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
Database();
|
|
||||||
|
|
||||||
public Q_SLOTS:
|
|
||||||
void scanQueue();
|
|
||||||
void scanDocuments(int folder_id, const QString &folder_path);
|
|
||||||
void addFolder(const QString &collection, const QString &path);
|
|
||||||
void removeFolder(const QString &collection, const QString &path);
|
|
||||||
void retrieveFromDB(const QList<QString> &collections, const QString &text);
|
|
||||||
void cleanDB();
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
|
||||||
void docsToScanChanged();
|
|
||||||
void retrieveResult(const QList<QString> &result);
|
|
||||||
void collectionListUpdated(const QList<CollectionItem> &collectionList);
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
|
||||||
void start();
|
|
||||||
void directoryChanged(const QString &path);
|
|
||||||
bool addFolderToWatch(const QString &path);
|
|
||||||
bool removeFolderFromWatch(const QString &path);
|
|
||||||
void addCurrentFolders();
|
|
||||||
void updateCollectionList();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void removeFolderInternal(const QString &collection, int folder_id, const QString &path);
|
|
||||||
void chunkStream(QTextStream &stream, int document_id);
|
|
||||||
void handleDocumentErrorAndScheduleNext(const QString &errorMessage,
|
|
||||||
int document_id, const QString &document_path, const QSqlError &error);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QQueue<DocumentInfo> m_docsToScan;
|
|
||||||
QList<QString> m_retrieve;
|
|
||||||
QThread m_dbThread;
|
|
||||||
QFileSystemWatcher *m_watcher;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LocalDocs : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_PROPERTY(LocalDocsModel *localDocsModel READ localDocsModel NOTIFY localDocsModelChanged)
|
|
||||||
|
|
||||||
public:
|
|
||||||
static LocalDocs *globalInstance();
|
|
||||||
|
|
||||||
LocalDocsModel *localDocsModel() const { return m_localDocsModel; }
|
|
||||||
|
|
||||||
void addFolder(const QString &collection, const QString &path);
|
|
||||||
void removeFolder(const QString &collection, const QString &path);
|
|
||||||
|
|
||||||
QList<QString> result() const { return m_retrieveResult; }
|
|
||||||
void requestRetrieve(const QList<QString> &collections, const QString &text);
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
|
||||||
void requestAddFolder(const QString &collection, const QString &path);
|
|
||||||
void requestRemoveFolder(const QString &collection, const QString &path);
|
|
||||||
void requestRetrieveFromDB(const QList<QString> &collections, const QString &text);
|
|
||||||
void receivedResult();
|
|
||||||
void localDocsModelChanged();
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
|
||||||
void handleRetrieveResult(const QList<QString> &result);
|
|
||||||
void handleCollectionListUpdated(const QList<CollectionItem> &collectionList);
|
|
||||||
|
|
||||||
private:
|
|
||||||
LocalDocsModel *m_localDocsModel;
|
|
||||||
Database *m_database;
|
|
||||||
QList<QString> m_retrieveResult;
|
|
||||||
QList<CollectionItem> m_collectionList;
|
|
||||||
|
|
||||||
private:
|
|
||||||
explicit LocalDocs();
|
|
||||||
~LocalDocs() {}
|
|
||||||
friend class MyLocalDocs;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // LOCALDOCS_H
|
|
@ -1,6 +1,7 @@
|
|||||||
#include "localdocsmodel.h"
|
#include "localdocsmodel.h"
|
||||||
|
|
||||||
#include "localdocs.h"
|
#include "localdocs.h"
|
||||||
|
#include "network.h"
|
||||||
|
|
||||||
LocalDocsCollectionsModel::LocalDocsCollectionsModel(QObject *parent)
|
LocalDocsCollectionsModel::LocalDocsCollectionsModel(QObject *parent)
|
||||||
: QSortFilterProxyModel(parent)
|
: QSortFilterProxyModel(parent)
|
||||||
@ -158,50 +159,43 @@ void LocalDocsModel::updateTotalEmbeddingsToIndex(int folder_id, size_t totalEmb
|
|||||||
[](CollectionItem& item, size_t val) { item.totalEmbeddingsToIndex += val; }, {TotalEmbeddingsToIndexRole});
|
[](CollectionItem& item, size_t val) { item.totalEmbeddingsToIndex += val; }, {TotalEmbeddingsToIndexRole});
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalDocsModel::addCollectionItem(const CollectionItem &item)
|
void LocalDocsModel::addCollectionItem(const CollectionItem &item, bool fromDb)
|
||||||
{
|
{
|
||||||
beginInsertRows(QModelIndex(), m_collectionList.size(), m_collectionList.size());
|
beginInsertRows(QModelIndex(), m_collectionList.size(), m_collectionList.size());
|
||||||
m_collectionList.append(item);
|
m_collectionList.append(item);
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
|
|
||||||
|
if (!fromDb) {
|
||||||
|
Network::globalInstance()->trackEvent("doc_collection_add", {
|
||||||
|
{"collection_count", m_collectionList.count()},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalDocsModel::removeCollectionIf(std::function<bool(CollectionItem)> const &predicate) {
|
||||||
|
for (int i = 0; i < m_collectionList.size();) {
|
||||||
|
if (predicate(m_collectionList.at(i))) {
|
||||||
|
beginRemoveRows(QModelIndex(), i, i);
|
||||||
|
m_collectionList.removeAt(i);
|
||||||
|
endRemoveRows();
|
||||||
|
|
||||||
|
Network::globalInstance()->trackEvent("doc_collection_remove", {
|
||||||
|
{"collection_count", m_collectionList.count()},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalDocsModel::removeFolderById(int folder_id)
|
void LocalDocsModel::removeFolderById(int folder_id)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < m_collectionList.size();) {
|
removeCollectionIf([folder_id](const auto &c) { return c.folder_id == folder_id; });
|
||||||
if (m_collectionList.at(i).folder_id == folder_id) {
|
|
||||||
beginRemoveRows(QModelIndex(), i, i);
|
|
||||||
m_collectionList.removeAt(i);
|
|
||||||
endRemoveRows();
|
|
||||||
} else {
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalDocsModel::removeCollectionPath(const QString &name, const QString &path)
|
void LocalDocsModel::removeCollectionPath(const QString &name, const QString &path)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < m_collectionList.size();) {
|
removeCollectionIf([&name, &path](const auto &c) { return c.collection == name && c.folder_path == path; });
|
||||||
if (m_collectionList.at(i).collection == name && m_collectionList.at(i).folder_path == path) {
|
|
||||||
beginRemoveRows(QModelIndex(), i, i);
|
|
||||||
m_collectionList.removeAt(i);
|
|
||||||
endRemoveRows();
|
|
||||||
} else {
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocalDocsModel::removeCollectionItem(const QString &collectionName)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < m_collectionList.size();) {
|
|
||||||
if (m_collectionList.at(i).collection == collectionName) {
|
|
||||||
beginRemoveRows(QModelIndex(), i, i);
|
|
||||||
m_collectionList.removeAt(i);
|
|
||||||
endRemoveRows();
|
|
||||||
} else {
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalDocsModel::collectionListUpdated(const QList<CollectionItem> &collectionList)
|
void LocalDocsModel::collectionListUpdated(const QList<CollectionItem> &collectionList)
|
||||||
|
@ -55,10 +55,9 @@ public Q_SLOTS:
|
|||||||
void updateTotalBytesToIndex(int folder_id, size_t totalBytesToIndex);
|
void updateTotalBytesToIndex(int folder_id, size_t totalBytesToIndex);
|
||||||
void updateCurrentEmbeddingsToIndex(int folder_id, size_t currentBytesToIndex);
|
void updateCurrentEmbeddingsToIndex(int folder_id, size_t currentBytesToIndex);
|
||||||
void updateTotalEmbeddingsToIndex(int folder_id, size_t totalBytesToIndex);
|
void updateTotalEmbeddingsToIndex(int folder_id, size_t totalBytesToIndex);
|
||||||
void addCollectionItem(const CollectionItem &item);
|
void addCollectionItem(const CollectionItem &item, bool fromDb);
|
||||||
void removeFolderById(int folder_id);
|
void removeFolderById(int folder_id);
|
||||||
void removeCollectionPath(const QString &name, const QString &path);
|
void removeCollectionPath(const QString &name, const QString &path);
|
||||||
void removeCollectionItem(const QString &collectionName);
|
|
||||||
void collectionListUpdated(const QList<CollectionItem> &collectionList);
|
void collectionListUpdated(const QList<CollectionItem> &collectionList);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -66,6 +65,7 @@ private:
|
|||||||
void updateField(int folder_id, T value,
|
void updateField(int folder_id, T value,
|
||||||
const std::function<void(CollectionItem&, T)>& updater,
|
const std::function<void(CollectionItem&, T)>& updater,
|
||||||
const QVector<int>& roles);
|
const QVector<int>& roles);
|
||||||
|
void removeCollectionIf(std::function<bool(CollectionItem)> const &predicate);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<CollectionItem> m_collectionList;
|
QList<CollectionItem> m_collectionList;
|
||||||
|
@ -910,15 +910,23 @@ bool MySettings::networkIsActive() const
|
|||||||
return setting.value("network/isActive", default_networkIsActive).toBool();
|
return setting.value("network/isActive", default_networkIsActive).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MySettings::isNetworkIsActiveSet() const
|
||||||
|
{
|
||||||
|
QSettings setting;
|
||||||
|
setting.sync();
|
||||||
|
return setting.value("network/isActive").isValid();
|
||||||
|
}
|
||||||
|
|
||||||
void MySettings::setNetworkIsActive(bool b)
|
void MySettings::setNetworkIsActive(bool b)
|
||||||
{
|
{
|
||||||
if (networkIsActive() == b)
|
|
||||||
return;
|
|
||||||
|
|
||||||
QSettings setting;
|
QSettings setting;
|
||||||
setting.setValue("network/isActive", b);
|
|
||||||
setting.sync();
|
setting.sync();
|
||||||
emit networkIsActiveChanged();
|
auto cur = setting.value("network/isActive");
|
||||||
|
if (!cur.isValid() || cur.toBool() != b) {
|
||||||
|
setting.setValue("network/isActive", b);
|
||||||
|
setting.sync();
|
||||||
|
emit networkIsActiveChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MySettings::networkUsageStatsActive() const
|
bool MySettings::networkUsageStatsActive() const
|
||||||
@ -928,13 +936,21 @@ bool MySettings::networkUsageStatsActive() const
|
|||||||
return setting.value("network/usageStatsActive", default_networkUsageStatsActive).toBool();
|
return setting.value("network/usageStatsActive", default_networkUsageStatsActive).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MySettings::isNetworkUsageStatsActiveSet() const
|
||||||
|
{
|
||||||
|
QSettings setting;
|
||||||
|
setting.sync();
|
||||||
|
return setting.value("network/usageStatsActive").isValid();
|
||||||
|
}
|
||||||
|
|
||||||
void MySettings::setNetworkUsageStatsActive(bool b)
|
void MySettings::setNetworkUsageStatsActive(bool b)
|
||||||
{
|
{
|
||||||
if (networkUsageStatsActive() == b)
|
|
||||||
return;
|
|
||||||
|
|
||||||
QSettings setting;
|
QSettings setting;
|
||||||
setting.setValue("network/usageStatsActive", b);
|
|
||||||
setting.sync();
|
setting.sync();
|
||||||
emit networkUsageStatsActiveChanged();
|
auto cur = setting.value("network/usageStatsActive");
|
||||||
|
if (!cur.isValid() || cur.toBool() != b) {
|
||||||
|
setting.setValue("network/usageStatsActive", b);
|
||||||
|
setting.sync();
|
||||||
|
emit networkUsageStatsActiveChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,8 +129,10 @@ public:
|
|||||||
QString networkAttribution() const;
|
QString networkAttribution() const;
|
||||||
void setNetworkAttribution(const QString &a);
|
void setNetworkAttribution(const QString &a);
|
||||||
bool networkIsActive() const;
|
bool networkIsActive() const;
|
||||||
|
Q_INVOKABLE bool isNetworkIsActiveSet() const;
|
||||||
void setNetworkIsActive(bool b);
|
void setNetworkIsActive(bool b);
|
||||||
bool networkUsageStatsActive() const;
|
bool networkUsageStatsActive() const;
|
||||||
|
Q_INVOKABLE bool isNetworkUsageStatsActiveSet() const;
|
||||||
void setNetworkUsageStatsActive(bool b);
|
void setNetworkUsageStatsActive(bool b);
|
||||||
int networkPort() const;
|
int networkPort() const;
|
||||||
void setNetworkPort(int c);
|
void setNetworkPort(int c);
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
#include "network.h"
|
#include "network.h"
|
||||||
#include "llm.h"
|
|
||||||
#include "chatlistmodel.h"
|
#include "chatlistmodel.h"
|
||||||
|
#include "download.h"
|
||||||
|
#include "llm.h"
|
||||||
|
#include "localdocs.h"
|
||||||
#include "mysettings.h"
|
#include "mysettings.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
@ -14,16 +19,49 @@
|
|||||||
|
|
||||||
//#define DEBUG
|
//#define DEBUG
|
||||||
|
|
||||||
|
static const char MIXPANEL_TOKEN[] = "ce362e568ddaee16ed243eaffb5860a2";
|
||||||
|
|
||||||
#if defined(Q_OS_MAC)
|
#if defined(Q_OS_MAC)
|
||||||
|
|
||||||
#include <sys/sysctl.h>
|
#include <sys/sysctl.h>
|
||||||
std::string getCPUModel() {
|
static QString getCPUModel() {
|
||||||
char buffer[256];
|
char buffer[256];
|
||||||
size_t bufferlen = sizeof(buffer);
|
size_t bufferlen = sizeof(buffer);
|
||||||
sysctlbyname("machdep.cpu.brand_string", &buffer, &bufferlen, NULL, 0);
|
sysctlbyname("machdep.cpu.brand_string", &buffer, &bufferlen, NULL, 0);
|
||||||
return std::string(buffer);
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#elif defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86)
|
||||||
|
|
||||||
|
#define get_cpuid(level, a, b, c, d) asm volatile("cpuid" : "=a" (a), "=b" (b), "=c" (c), "=d" (d) : "0" (level) : "memory")
|
||||||
|
|
||||||
|
static QString getCPUModel() {
|
||||||
|
unsigned regs[12];
|
||||||
|
|
||||||
|
// EAX=800000000h: Get Highest Extended Function Implemented
|
||||||
|
get_cpuid(0x80000000, regs[0], regs[1], regs[2], regs[3]);
|
||||||
|
if (regs[0] < 0x80000004)
|
||||||
|
return "(unknown)";
|
||||||
|
|
||||||
|
// EAX=800000002h-800000004h: Processor Brand String
|
||||||
|
get_cpuid(0x80000002, regs[0], regs[1], regs[ 2], regs[ 3]);
|
||||||
|
get_cpuid(0x80000003, regs[4], regs[5], regs[ 6], regs[ 7]);
|
||||||
|
get_cpuid(0x80000004, regs[8], regs[9], regs[10], regs[11]);
|
||||||
|
|
||||||
|
char str[sizeof(regs) + 1];
|
||||||
|
memcpy(str, regs, sizeof(regs));
|
||||||
|
str[sizeof(regs)] = 0;
|
||||||
|
|
||||||
|
return QString(str).trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
static QString getCPUModel() { return "(non-x86)"; }
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
class MyNetwork: public Network { };
|
class MyNetwork: public Network { };
|
||||||
Q_GLOBAL_STATIC(MyNetwork, networkInstance)
|
Q_GLOBAL_STATIC(MyNetwork, networkInstance)
|
||||||
Network *Network::globalInstance()
|
Network *Network::globalInstance()
|
||||||
@ -33,40 +71,43 @@ Network *Network::globalInstance()
|
|||||||
|
|
||||||
Network::Network()
|
Network::Network()
|
||||||
: QObject{nullptr}
|
: QObject{nullptr}
|
||||||
, m_shouldSendStartup(false)
|
|
||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
settings.sync();
|
settings.sync();
|
||||||
m_uniqueId = settings.value("uniqueId", generateUniqueId()).toString();
|
m_uniqueId = settings.value("uniqueId", generateUniqueId()).toString();
|
||||||
settings.setValue("uniqueId", m_uniqueId);
|
settings.setValue("uniqueId", m_uniqueId);
|
||||||
settings.sync();
|
settings.sync();
|
||||||
connect(MySettings::globalInstance(), &MySettings::networkIsActiveChanged, this, &Network::handleIsActiveChanged);
|
m_sessionId = generateUniqueId();
|
||||||
connect(MySettings::globalInstance(), &MySettings::networkUsageStatsActiveChanged, this, &Network::handleUsageStatsActiveChanged);
|
|
||||||
if (MySettings::globalInstance()->networkIsActive())
|
// allow sendMixpanel to be called from any thread
|
||||||
|
connect(this, &Network::requestMixpanel, this, &Network::sendMixpanel, Qt::QueuedConnection);
|
||||||
|
|
||||||
|
const auto *mySettings = MySettings::globalInstance();
|
||||||
|
connect(mySettings, &MySettings::networkIsActiveChanged, this, &Network::handleIsActiveChanged);
|
||||||
|
connect(mySettings, &MySettings::networkUsageStatsActiveChanged, this, &Network::handleUsageStatsActiveChanged);
|
||||||
|
|
||||||
|
m_hasSentOptIn = !Download::globalInstance()->isFirstStart() && mySettings->networkUsageStatsActive();
|
||||||
|
m_hasSentOptOut = !Download::globalInstance()->isFirstStart() && !mySettings->networkUsageStatsActive();
|
||||||
|
|
||||||
|
if (mySettings->networkIsActive())
|
||||||
sendHealth();
|
sendHealth();
|
||||||
if (MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
sendIpify();
|
|
||||||
connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this,
|
connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this,
|
||||||
&Network::handleSslErrors);
|
&Network::handleSslErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: this won't be useful until we make it possible to change this via the settings page
|
||||||
|
void Network::handleUsageStatsActiveChanged()
|
||||||
|
{
|
||||||
|
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
||||||
|
m_sendUsageStats = false;
|
||||||
|
}
|
||||||
|
|
||||||
void Network::handleIsActiveChanged()
|
void Network::handleIsActiveChanged()
|
||||||
{
|
{
|
||||||
if (MySettings::globalInstance()->networkUsageStatsActive())
|
if (MySettings::globalInstance()->networkUsageStatsActive())
|
||||||
sendHealth();
|
sendHealth();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Network::handleUsageStatsActiveChanged()
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
sendOptOut();
|
|
||||||
else {
|
|
||||||
// model might be loaded already when user opt-in for first time
|
|
||||||
sendStartup();
|
|
||||||
sendIpify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Network::generateUniqueId() const
|
QString Network::generateUniqueId() const
|
||||||
{
|
{
|
||||||
return QUuid::createUuid().toString(QUuid::WithoutBraces);
|
return QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||||
@ -167,8 +208,8 @@ void Network::handleSslErrors(QNetworkReply *reply, const QList<QSslError> &erro
|
|||||||
void Network::sendOptOut()
|
void Network::sendOptOut()
|
||||||
{
|
{
|
||||||
QJsonObject properties;
|
QJsonObject properties;
|
||||||
properties.insert("token", "ce362e568ddaee16ed243eaffb5860a2");
|
properties.insert("token", MIXPANEL_TOKEN);
|
||||||
properties.insert("time", QDateTime::currentSecsSinceEpoch());
|
properties.insert("time", QDateTime::currentMSecsSinceEpoch());
|
||||||
properties.insert("distinct_id", m_uniqueId);
|
properties.insert("distinct_id", m_uniqueId);
|
||||||
properties.insert("$insert_id", generateUniqueId());
|
properties.insert("$insert_id", generateUniqueId());
|
||||||
|
|
||||||
@ -181,7 +222,7 @@ void Network::sendOptOut()
|
|||||||
|
|
||||||
QJsonDocument doc;
|
QJsonDocument doc;
|
||||||
doc.setArray(array);
|
doc.setArray(array);
|
||||||
sendMixpanel(doc.toJson(QJsonDocument::Compact), true /*isOptOut*/);
|
emit requestMixpanel(doc.toJson(QJsonDocument::Compact), true /*isOptOut*/);
|
||||||
|
|
||||||
#if defined(DEBUG)
|
#if defined(DEBUG)
|
||||||
printf("%s %s\n", qPrintable("opt_out"), qPrintable(doc.toJson(QJsonDocument::Indented)));
|
printf("%s %s\n", qPrintable("opt_out"), qPrintable(doc.toJson(QJsonDocument::Indented)));
|
||||||
@ -189,215 +230,76 @@ void Network::sendOptOut()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void Network::sendModelLoaded()
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
sendMixpanelEvent("model_load");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendResetContext(int conversationLength)
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
|
|
||||||
KeyValue kv;
|
|
||||||
kv.key = QString("length");
|
|
||||||
kv.value = QJsonValue(conversationLength);
|
|
||||||
sendMixpanelEvent("reset_context", QVector<KeyValue>{kv});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendStartup()
|
void Network::sendStartup()
|
||||||
{
|
{
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
const auto *mySettings = MySettings::globalInstance();
|
||||||
|
Q_ASSERT(mySettings->isNetworkUsageStatsActiveSet());
|
||||||
|
if (!mySettings->networkUsageStatsActive()) {
|
||||||
|
// send a single opt-out per session after the user has made their selections,
|
||||||
|
// unless this is a normal start (same version) and the user was already opted out
|
||||||
|
if (!m_hasSentOptOut) {
|
||||||
|
sendOptOut();
|
||||||
|
m_hasSentOptOut = true;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
m_shouldSendStartup = true;
|
}
|
||||||
if (m_ipify.isEmpty())
|
|
||||||
return; // when it completes it will send
|
// only chance to enable usage stats is at the start of a new session
|
||||||
sendMixpanelEvent("startup");
|
m_sendUsageStats = true;
|
||||||
|
|
||||||
|
const auto *display = QGuiApplication::primaryScreen();
|
||||||
|
trackEvent("startup", {
|
||||||
|
{"$screen_dpi", std::round(display->physicalDotsPerInch())},
|
||||||
|
{"display", QString("%1x%2").arg(display->size().width()).arg(display->size().height())},
|
||||||
|
{"ram", LLM::globalInstance()->systemTotalRAMInGB()},
|
||||||
|
{"cpu", getCPUModel()},
|
||||||
|
{"datalake_active", mySettings->networkIsActive()},
|
||||||
|
});
|
||||||
|
sendIpify();
|
||||||
|
|
||||||
|
// mirror opt-out logic so the ratio can be used to infer totals
|
||||||
|
if (!m_hasSentOptIn) {
|
||||||
|
trackEvent("opt_in");
|
||||||
|
m_hasSentOptIn = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Network::sendCheckForUpdates()
|
void Network::trackChatEvent(const QString &ev, QVariantMap props)
|
||||||
{
|
{
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
const auto &curChat = ChatListModel::globalInstance()->currentChat();
|
||||||
return;
|
if (!props.contains("model"))
|
||||||
sendMixpanelEvent("check_for_updates");
|
props.insert("model", curChat->modelInfo().filename());
|
||||||
|
props.insert("actualDevice", curChat->device());
|
||||||
|
props.insert("doc_collections_enabled", curChat->collectionList().count());
|
||||||
|
props.insert("doc_collections_total", LocalDocs::globalInstance()->localDocsModel()->rowCount());
|
||||||
|
props.insert("datalake_active", MySettings::globalInstance()->networkIsActive());
|
||||||
|
props.insert("using_server", ChatListModel::globalInstance()->currentChat()->isServer());
|
||||||
|
trackEvent(ev, props);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Network::sendModelDownloaderDialog()
|
void Network::trackEvent(const QString &ev, const QVariantMap &props)
|
||||||
{
|
{
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
if (!m_sendUsageStats)
|
||||||
return;
|
|
||||||
sendMixpanelEvent("download_dialog");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendInstallModel(const QString &model)
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
KeyValue kv;
|
|
||||||
kv.key = QString("model");
|
|
||||||
kv.value = QJsonValue(model);
|
|
||||||
sendMixpanelEvent("install_model", QVector<KeyValue>{kv});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendRemoveModel(const QString &model)
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
KeyValue kv;
|
|
||||||
kv.key = QString("model");
|
|
||||||
kv.value = QJsonValue(model);
|
|
||||||
sendMixpanelEvent("remove_model", QVector<KeyValue>{kv});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendDownloadStarted(const QString &model)
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
KeyValue kv;
|
|
||||||
kv.key = QString("model");
|
|
||||||
kv.value = QJsonValue(model);
|
|
||||||
sendMixpanelEvent("download_started", QVector<KeyValue>{kv});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendDownloadCanceled(const QString &model)
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
KeyValue kv;
|
|
||||||
kv.key = QString("model");
|
|
||||||
kv.value = QJsonValue(model);
|
|
||||||
sendMixpanelEvent("download_canceled", QVector<KeyValue>{kv});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendDownloadError(const QString &model, int code, const QString &errorString)
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
KeyValue kv;
|
|
||||||
kv.key = QString("model");
|
|
||||||
kv.value = QJsonValue(model);
|
|
||||||
KeyValue kvCode;
|
|
||||||
kvCode.key = QString("code");
|
|
||||||
kvCode.value = QJsonValue(code);
|
|
||||||
KeyValue kvError;
|
|
||||||
kvError.key = QString("error");
|
|
||||||
kvError.value = QJsonValue(errorString);
|
|
||||||
sendMixpanelEvent("download_error", QVector<KeyValue>{kv, kvCode, kvError});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendDownloadFinished(const QString &model, bool success)
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
KeyValue kv;
|
|
||||||
kv.key = QString("model");
|
|
||||||
kv.value = QJsonValue(model);
|
|
||||||
KeyValue kvSuccess;
|
|
||||||
kvSuccess.key = QString("success");
|
|
||||||
kvSuccess.value = QJsonValue(success);
|
|
||||||
sendMixpanelEvent("download_finished", QVector<KeyValue>{kv, kvSuccess});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendSettingsDialog()
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
sendMixpanelEvent("settings_dialog");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendNetworkToggled(bool isActive)
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
KeyValue kv;
|
|
||||||
kv.key = QString("isActive");
|
|
||||||
kv.value = QJsonValue(isActive);
|
|
||||||
sendMixpanelEvent("network_toggled", QVector<KeyValue>{kv});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendNewChat(int count)
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
KeyValue kv;
|
|
||||||
kv.key = QString("number_of_chats");
|
|
||||||
kv.value = QJsonValue(count);
|
|
||||||
sendMixpanelEvent("new_chat", QVector<KeyValue>{kv});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendRemoveChat()
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
sendMixpanelEvent("remove_chat");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendRenameChat()
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
sendMixpanelEvent("rename_chat");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendChatStarted()
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
sendMixpanelEvent("chat_started");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendRecalculatingContext(int conversationLength)
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
|
|
||||||
KeyValue kv;
|
|
||||||
kv.key = QString("length");
|
|
||||||
kv.value = QJsonValue(conversationLength);
|
|
||||||
sendMixpanelEvent("recalc_context", QVector<KeyValue>{kv});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendNonCompatHardware()
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
|
||||||
sendMixpanelEvent("noncompat_hardware");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::sendMixpanelEvent(const QString &ev, const QVector<KeyValue> &values)
|
|
||||||
{
|
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Q_ASSERT(ChatListModel::globalInstance()->currentChat());
|
Q_ASSERT(ChatListModel::globalInstance()->currentChat());
|
||||||
QJsonObject properties;
|
QJsonObject properties;
|
||||||
properties.insert("token", "ce362e568ddaee16ed243eaffb5860a2");
|
|
||||||
properties.insert("time", QDateTime::currentSecsSinceEpoch());
|
properties.insert("token", MIXPANEL_TOKEN);
|
||||||
properties.insert("distinct_id", m_uniqueId);
|
if (!props.contains("time"))
|
||||||
|
properties.insert("time", QDateTime::currentMSecsSinceEpoch());
|
||||||
|
properties.insert("distinct_id", m_uniqueId); // effectively a device ID
|
||||||
properties.insert("$insert_id", generateUniqueId());
|
properties.insert("$insert_id", generateUniqueId());
|
||||||
properties.insert("$os", QSysInfo::prettyProductName());
|
|
||||||
if (!m_ipify.isEmpty())
|
if (!m_ipify.isEmpty())
|
||||||
properties.insert("ip", m_ipify);
|
properties.insert("ip", m_ipify);
|
||||||
properties.insert("name", QCoreApplication::applicationName() + " v"
|
|
||||||
+ QCoreApplication::applicationVersion());
|
|
||||||
properties.insert("model", ChatListModel::globalInstance()->currentChat()->modelInfo().filename());
|
|
||||||
properties.insert("requestedDevice", MySettings::globalInstance()->device());
|
|
||||||
properties.insert("actualDevice", ChatListModel::globalInstance()->currentChat()->device());
|
|
||||||
|
|
||||||
// Some additional startup information
|
properties.insert("$os", QSysInfo::prettyProductName());
|
||||||
if (ev == "startup") {
|
properties.insert("session_id", m_sessionId);
|
||||||
const QSize display = QGuiApplication::primaryScreen()->size();
|
properties.insert("name", QCoreApplication::applicationName() + " v" + QCoreApplication::applicationVersion());
|
||||||
properties.insert("display", QString("%1x%2").arg(display.width()).arg(display.height()));
|
|
||||||
properties.insert("ram", LLM::globalInstance()->systemTotalRAMInGB());
|
|
||||||
#if defined(Q_OS_MAC)
|
|
||||||
properties.insert("cpu", QString::fromStdString(getCPUModel()));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& p : values)
|
for (const auto &[key, value]: props.asKeyValueRange())
|
||||||
properties.insert(p.key, p.value);
|
properties.insert(key, QJsonValue::fromVariant(value));
|
||||||
|
|
||||||
QJsonObject event;
|
QJsonObject event;
|
||||||
event.insert("event", ev);
|
event.insert("event", ev);
|
||||||
@ -408,7 +310,7 @@ void Network::sendMixpanelEvent(const QString &ev, const QVector<KeyValue> &valu
|
|||||||
|
|
||||||
QJsonDocument doc;
|
QJsonDocument doc;
|
||||||
doc.setArray(array);
|
doc.setArray(array);
|
||||||
sendMixpanel(doc.toJson(QJsonDocument::Compact));
|
emit requestMixpanel(doc.toJson(QJsonDocument::Compact));
|
||||||
|
|
||||||
#if defined(DEBUG)
|
#if defined(DEBUG)
|
||||||
printf("%s %s\n", qPrintable(ev), qPrintable(doc.toJson(QJsonDocument::Indented)));
|
printf("%s %s\n", qPrintable(ev), qPrintable(doc.toJson(QJsonDocument::Indented)));
|
||||||
@ -418,7 +320,7 @@ void Network::sendMixpanelEvent(const QString &ev, const QVector<KeyValue> &valu
|
|||||||
|
|
||||||
void Network::sendIpify()
|
void Network::sendIpify()
|
||||||
{
|
{
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive() || !m_ipify.isEmpty())
|
if (!m_sendUsageStats || !m_ipify.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QUrl ipifyUrl("https://api.ipify.org");
|
QUrl ipifyUrl("https://api.ipify.org");
|
||||||
@ -433,7 +335,7 @@ void Network::sendIpify()
|
|||||||
|
|
||||||
void Network::sendMixpanel(const QByteArray &json, bool isOptOut)
|
void Network::sendMixpanel(const QByteArray &json, bool isOptOut)
|
||||||
{
|
{
|
||||||
if (!MySettings::globalInstance()->networkUsageStatsActive() && !isOptOut)
|
if (!m_sendUsageStats)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QUrl trackUrl("https://api.mixpanel.com/track");
|
QUrl trackUrl("https://api.mixpanel.com/track");
|
||||||
@ -449,7 +351,6 @@ void Network::sendMixpanel(const QByteArray &json, bool isOptOut)
|
|||||||
|
|
||||||
void Network::handleIpifyFinished()
|
void Network::handleIpifyFinished()
|
||||||
{
|
{
|
||||||
Q_ASSERT(MySettings::globalInstance()->networkUsageStatsActive());
|
|
||||||
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
||||||
if (!reply)
|
if (!reply)
|
||||||
return;
|
return;
|
||||||
@ -469,8 +370,7 @@ void Network::handleIpifyFinished()
|
|||||||
#endif
|
#endif
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
if (m_shouldSendStartup)
|
trackEvent("ipify_complete");
|
||||||
sendStartup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Network::handleMixpanelFinished()
|
void Network::handleMixpanelFinished()
|
||||||
|
@ -19,31 +19,15 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE QString generateUniqueId() const;
|
Q_INVOKABLE QString generateUniqueId() const;
|
||||||
Q_INVOKABLE bool sendConversation(const QString &ingestId, const QString &conversation);
|
Q_INVOKABLE bool sendConversation(const QString &ingestId, const QString &conversation);
|
||||||
|
Q_INVOKABLE void trackChatEvent(const QString &event, QVariantMap props = QVariantMap());
|
||||||
|
Q_INVOKABLE void trackEvent(const QString &event, const QVariantMap &props = QVariantMap());
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void healthCheckFailed(int code);
|
void healthCheckFailed(int code);
|
||||||
|
void requestMixpanel(const QByteArray &json, bool isOptOut = false);
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void sendOptOut();
|
|
||||||
void sendModelLoaded();
|
|
||||||
void sendStartup();
|
void sendStartup();
|
||||||
void sendCheckForUpdates();
|
|
||||||
Q_INVOKABLE void sendModelDownloaderDialog();
|
|
||||||
Q_INVOKABLE void sendResetContext(int conversationLength);
|
|
||||||
void sendInstallModel(const QString &model);
|
|
||||||
void sendRemoveModel(const QString &model);
|
|
||||||
void sendDownloadStarted(const QString &model);
|
|
||||||
void sendDownloadCanceled(const QString &model);
|
|
||||||
void sendDownloadError(const QString &model, int code, const QString &errorString);
|
|
||||||
void sendDownloadFinished(const QString &model, bool success);
|
|
||||||
Q_INVOKABLE void sendSettingsDialog();
|
|
||||||
Q_INVOKABLE void sendNetworkToggled(bool active);
|
|
||||||
Q_INVOKABLE void sendNewChat(int count);
|
|
||||||
Q_INVOKABLE void sendRemoveChat();
|
|
||||||
Q_INVOKABLE void sendRenameChat();
|
|
||||||
Q_INVOKABLE void sendNonCompatHardware();
|
|
||||||
void sendChatStarted();
|
|
||||||
void sendRecalculatingContext(int conversationLength);
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void handleIpifyFinished();
|
void handleIpifyFinished();
|
||||||
@ -53,18 +37,21 @@ private Q_SLOTS:
|
|||||||
void handleMixpanelFinished();
|
void handleMixpanelFinished();
|
||||||
void handleIsActiveChanged();
|
void handleIsActiveChanged();
|
||||||
void handleUsageStatsActiveChanged();
|
void handleUsageStatsActiveChanged();
|
||||||
|
void sendMixpanel(const QByteArray &json, bool isOptOut);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void sendOptOut();
|
||||||
void sendHealth();
|
void sendHealth();
|
||||||
void sendIpify();
|
void sendIpify();
|
||||||
void sendMixpanelEvent(const QString &event, const QVector<KeyValue> &values = QVector<KeyValue>());
|
|
||||||
void sendMixpanel(const QByteArray &json, bool isOptOut = false);
|
|
||||||
bool packageAndSendJson(const QString &ingestId, const QString &json);
|
bool packageAndSendJson(const QString &ingestId, const QString &json);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_shouldSendStartup;
|
bool m_sendUsageStats = false;
|
||||||
|
bool m_hasSentOptIn;
|
||||||
|
bool m_hasSentOptOut;
|
||||||
QString m_ipify;
|
QString m_ipify;
|
||||||
QString m_uniqueId;
|
QString m_uniqueId;
|
||||||
|
QString m_sessionId;
|
||||||
QNetworkAccessManager m_networkManager;
|
QNetworkAccessManager m_networkManager;
|
||||||
QVector<QNetworkReply*> m_activeUploads;
|
QVector<QNetworkReply*> m_activeUploads;
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ Rectangle {
|
|||||||
Accessible.description: qsTr("Create a new chat")
|
Accessible.description: qsTr("Create a new chat")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
ChatListModel.addChat();
|
ChatListModel.addChat();
|
||||||
Network.sendNewChat(ChatListModel.count)
|
Network.trackEvent("new_chat", {"number_of_chats": ChatListModel.count})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,8 +110,8 @@ Rectangle {
|
|||||||
// having focus
|
// having focus
|
||||||
if (chatName.readOnly)
|
if (chatName.readOnly)
|
||||||
return;
|
return;
|
||||||
|
Network.trackChatEvent("rename_chat")
|
||||||
changeName();
|
changeName();
|
||||||
Network.sendRenameChat()
|
|
||||||
}
|
}
|
||||||
function changeName() {
|
function changeName() {
|
||||||
ChatListModel.get(index).name = chatName.text
|
ChatListModel.get(index).name = chatName.text
|
||||||
@ -194,8 +194,8 @@ Rectangle {
|
|||||||
color: "transparent"
|
color: "transparent"
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
Network.trackChatEvent("remove_chat")
|
||||||
ChatListModel.removeChat(ChatListModel.get(index))
|
ChatListModel.removeChat(ChatListModel.get(index))
|
||||||
Network.sendRemoveChat()
|
|
||||||
}
|
}
|
||||||
Accessible.role: Accessible.Button
|
Accessible.role: Accessible.Button
|
||||||
Accessible.name: qsTr("Confirm chat deletion")
|
Accessible.name: qsTr("Confirm chat deletion")
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
import QtCore
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Controls.Basic
|
import QtQuick.Controls.Basic
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Qt5Compat.GraphicalEffects
|
|
||||||
import llm
|
|
||||||
import chatlistmodel
|
import chatlistmodel
|
||||||
import download
|
import download
|
||||||
import modellist
|
|
||||||
import network
|
|
||||||
import gpt4all
|
import gpt4all
|
||||||
|
import llm
|
||||||
|
import localdocs
|
||||||
|
import modellist
|
||||||
import mysettings
|
import mysettings
|
||||||
|
import network
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: window
|
id: window
|
||||||
@ -29,6 +31,10 @@ Rectangle {
|
|||||||
startupDialogs();
|
startupDialogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component.onDestruction: {
|
||||||
|
Network.trackEvent("session_end")
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: firstStartDialog
|
target: firstStartDialog
|
||||||
function onClosed() {
|
function onClosed() {
|
||||||
@ -66,12 +72,12 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
property bool hasShownModelDownload: false
|
property bool hasShownModelDownload: false
|
||||||
property bool hasShownFirstStart: false
|
property bool hasCheckedFirstStart: false
|
||||||
property bool hasShownSettingsAccess: false
|
property bool hasShownSettingsAccess: false
|
||||||
|
|
||||||
function startupDialogs() {
|
function startupDialogs() {
|
||||||
if (!LLM.compatHardware()) {
|
if (!LLM.compatHardware()) {
|
||||||
Network.sendNonCompatHardware();
|
Network.trackEvent("noncompat_hardware")
|
||||||
errorCompatHardware.open();
|
errorCompatHardware.open();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -84,10 +90,18 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check for first time start of this version
|
// check for first time start of this version
|
||||||
if (!hasShownFirstStart && Download.isFirstStart()) {
|
if (!hasCheckedFirstStart) {
|
||||||
firstStartDialog.open();
|
if (Download.isFirstStart(/*writeVersion*/ true)) {
|
||||||
hasShownFirstStart = true;
|
firstStartDialog.open();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send startup or opt-out now that the user has made their choice
|
||||||
|
Network.sendStartup()
|
||||||
|
// start localdocs
|
||||||
|
LocalDocs.requestStart()
|
||||||
|
|
||||||
|
hasCheckedFirstStart = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for any current models and if not, open download dialog once
|
// check for any current models and if not, open download dialog once
|
||||||
@ -547,7 +561,6 @@ Rectangle {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
if (MySettings.networkIsActive) {
|
if (MySettings.networkIsActive) {
|
||||||
MySettings.networkIsActive = false
|
MySettings.networkIsActive = false
|
||||||
Network.sendNetworkToggled(false);
|
|
||||||
} else
|
} else
|
||||||
networkDialog.open()
|
networkDialog.open()
|
||||||
}
|
}
|
||||||
@ -733,7 +746,7 @@ Rectangle {
|
|||||||
Accessible.description: qsTr("Reset the context and erase current conversation")
|
Accessible.description: qsTr("Reset the context and erase current conversation")
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
Network.sendResetContext(chatModel.count)
|
Network.trackChatEvent("reset_context", { "length": chatModel.count })
|
||||||
currentChat.reset();
|
currentChat.reset();
|
||||||
currentChat.processSystemPrompt();
|
currentChat.processSystemPrompt();
|
||||||
}
|
}
|
||||||
@ -1288,9 +1301,11 @@ Rectangle {
|
|||||||
var listElement = chatModel.get(index);
|
var listElement = chatModel.get(index);
|
||||||
|
|
||||||
if (currentChat.responseInProgress) {
|
if (currentChat.responseInProgress) {
|
||||||
|
Network.trackChatEvent("stop_generating_clicked")
|
||||||
listElement.stopped = true
|
listElement.stopped = true
|
||||||
currentChat.stopGenerating()
|
currentChat.stopGenerating()
|
||||||
} else {
|
} else {
|
||||||
|
Network.trackChatEvent("regenerate_clicked")
|
||||||
currentChat.regenerateResponse()
|
currentChat.regenerateResponse()
|
||||||
if (chatModel.count) {
|
if (chatModel.count) {
|
||||||
if (listElement.name === qsTr("Response: ")) {
|
if (listElement.name === qsTr("Response: ")) {
|
||||||
@ -1405,6 +1420,7 @@ Rectangle {
|
|||||||
if (textInput.text === "")
|
if (textInput.text === "")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
Network.trackChatEvent("send_message")
|
||||||
currentChat.stopGenerating()
|
currentChat.stopGenerating()
|
||||||
currentChat.newPromptResponsePair(textInput.text);
|
currentChat.newPromptResponsePair(textInput.text);
|
||||||
currentChat.prompt(textInput.text,
|
currentChat.prompt(textInput.text,
|
||||||
|
@ -19,7 +19,7 @@ MyDialog {
|
|||||||
property bool showEmbeddingModels: false
|
property bool showEmbeddingModels: false
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
Network.sendModelDownloaderDialog();
|
Network.trackEvent("download_dialog")
|
||||||
|
|
||||||
if (showEmbeddingModels) {
|
if (showEmbeddingModels) {
|
||||||
ModelList.downloadableModels.expanded = true
|
ModelList.downloadableModels.expanded = true
|
||||||
|
@ -100,16 +100,10 @@ NOTE: By turning on this feature, you will be sending your data to the GPT4All O
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
if (MySettings.networkIsActive)
|
MySettings.networkIsActive = true
|
||||||
return
|
|
||||||
MySettings.networkIsActive = true;
|
|
||||||
Network.sendNetworkToggled(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onRejected: {
|
onRejected: {
|
||||||
if (!MySettings.networkIsActive)
|
MySettings.networkIsActive = false
|
||||||
return
|
|
||||||
MySettings.networkIsActive = false;
|
|
||||||
Network.sendNetworkToggled(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ MyDialog {
|
|||||||
modal: true
|
modal: true
|
||||||
padding: 20
|
padding: 20
|
||||||
onOpened: {
|
onOpened: {
|
||||||
Network.sendSettingsDialog();
|
Network.trackEvent("settings_dialog")
|
||||||
}
|
}
|
||||||
|
|
||||||
signal downloadClicked
|
signal downloadClicked
|
||||||
|
@ -123,8 +123,6 @@ model release that uses your data!")
|
|||||||
buttons: optInStatisticsRadio.children
|
buttons: optInStatisticsRadio.children
|
||||||
onClicked: {
|
onClicked: {
|
||||||
MySettings.networkUsageStatsActive = optInStatisticsRadio.checked
|
MySettings.networkUsageStatsActive = optInStatisticsRadio.checked
|
||||||
if (!optInStatisticsRadio.checked)
|
|
||||||
Network.sendOptOut();
|
|
||||||
if (optInNetworkRadio.choiceMade && optInStatisticsRadio.choiceMade)
|
if (optInNetworkRadio.choiceMade && optInStatisticsRadio.choiceMade)
|
||||||
startupDialog.close();
|
startupDialog.close();
|
||||||
}
|
}
|
||||||
@ -140,7 +138,7 @@ model release that uses your data!")
|
|||||||
|
|
||||||
RadioButton {
|
RadioButton {
|
||||||
id: optInStatisticsRadioYes
|
id: optInStatisticsRadioYes
|
||||||
checked: false
|
checked: MySettings.networkUsageStatsActive
|
||||||
text: qsTr("Yes")
|
text: qsTr("Yes")
|
||||||
font.pixelSize: theme.fontSizeLarge
|
font.pixelSize: theme.fontSizeLarge
|
||||||
Accessible.role: Accessible.RadioButton
|
Accessible.role: Accessible.RadioButton
|
||||||
@ -182,6 +180,7 @@ model release that uses your data!")
|
|||||||
}
|
}
|
||||||
RadioButton {
|
RadioButton {
|
||||||
id: optInStatisticsRadioNo
|
id: optInStatisticsRadioNo
|
||||||
|
checked: MySettings.isNetworkUsageStatsActiveSet() && !MySettings.networkUsageStatsActive
|
||||||
text: qsTr("No")
|
text: qsTr("No")
|
||||||
font.pixelSize: theme.fontSizeLarge
|
font.pixelSize: theme.fontSizeLarge
|
||||||
Accessible.role: Accessible.RadioButton
|
Accessible.role: Accessible.RadioButton
|
||||||
@ -254,7 +253,7 @@ model release that uses your data!")
|
|||||||
|
|
||||||
RadioButton {
|
RadioButton {
|
||||||
id: optInNetworkRadioYes
|
id: optInNetworkRadioYes
|
||||||
checked: false
|
checked: MySettings.networkIsActive
|
||||||
text: qsTr("Yes")
|
text: qsTr("Yes")
|
||||||
font.pixelSize: theme.fontSizeLarge
|
font.pixelSize: theme.fontSizeLarge
|
||||||
Accessible.role: Accessible.RadioButton
|
Accessible.role: Accessible.RadioButton
|
||||||
@ -296,6 +295,7 @@ model release that uses your data!")
|
|||||||
}
|
}
|
||||||
RadioButton {
|
RadioButton {
|
||||||
id: optInNetworkRadioNo
|
id: optInNetworkRadioNo
|
||||||
|
checked: MySettings.isNetworkIsActiveSet() && !MySettings.networkIsActive
|
||||||
text: qsTr("No")
|
text: qsTr("No")
|
||||||
font.pixelSize: theme.fontSizeLarge
|
font.pixelSize: theme.fontSizeLarge
|
||||||
Accessible.role: Accessible.RadioButton
|
Accessible.role: Accessible.RadioButton
|
||||||
|
Loading…
Reference in New Issue
Block a user