chat: major UI redesign for v3.0.0 (#2396)

Signed-off-by: Adam Treat <treat.adam@gmail.com>
Signed-off-by: Jared Van Bortel <jared@nomic.ai>
Co-authored-by: Jared Van Bortel <jared@nomic.ai>
This commit is contained in:
AT 2024-06-24 18:49:23 -04:00 committed by GitHub
parent 1272b694ae
commit 9273b49b62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
111 changed files with 8540 additions and 7879 deletions

3
.gitmodules vendored
View File

@ -2,3 +2,6 @@
path = gpt4all-backend/llama.cpp-mainline path = gpt4all-backend/llama.cpp-mainline
url = https://github.com/nomic-ai/llama.cpp.git url = https://github.com/nomic-ai/llama.cpp.git
branch = master branch = master
[submodule "gpt4all-chat/usearch"]
path = gpt4all-chat/usearch
url = https://github.com/unum-cloud/usearch.git

View File

@ -20,24 +20,28 @@ namespace fs = std::filesystem;
#ifndef _WIN32 #ifndef _WIN32
Dlhandle::Dlhandle(const fs::path &fpath) { Dlhandle::Dlhandle(const fs::path &fpath)
{
chandle = dlopen(fpath.c_str(), RTLD_LAZY | RTLD_LOCAL); chandle = dlopen(fpath.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!chandle) { if (!chandle) {
throw Exception("dlopen: "s + dlerror()); throw Exception("dlopen: "s + dlerror());
} }
} }
Dlhandle::~Dlhandle() { Dlhandle::~Dlhandle()
{
if (chandle) dlclose(chandle); if (chandle) dlclose(chandle);
} }
void *Dlhandle::get_internal(const char *symbol) const { void *Dlhandle::get_internal(const char *symbol) const
{
return dlsym(chandle, symbol); return dlsym(chandle, symbol);
} }
#else // defined(_WIN32) #else // defined(_WIN32)
Dlhandle::Dlhandle(const fs::path &fpath) { Dlhandle::Dlhandle(const fs::path &fpath)
{
fs::path afpath = fs::absolute(fpath); fs::path afpath = fs::absolute(fpath);
// Suppress the "Entry Point Not Found" dialog, caused by outdated nvcuda.dll from the GPU driver // Suppress the "Entry Point Not Found" dialog, caused by outdated nvcuda.dll from the GPU driver
@ -58,11 +62,13 @@ Dlhandle::Dlhandle(const fs::path &fpath) {
} }
} }
Dlhandle::~Dlhandle() { Dlhandle::~Dlhandle()
{
if (chandle) FreeLibrary(HMODULE(chandle)); if (chandle) FreeLibrary(HMODULE(chandle));
} }
void *Dlhandle::get_internal(const char *symbol) const { void *Dlhandle::get_internal(const char *symbol) const
{
return GetProcAddress(HMODULE(chandle), symbol); return GetProcAddress(HMODULE(chandle), symbol);
} }

View File

@ -123,7 +123,8 @@ static bool kv_cache_init(
} }
// load the model's weights from a file path // load the model's weights from a file path
bool gptj_model_load(const std::string &fname, gptj_model & model, gpt_vocab & vocab, size_t * mem_req = nullptr) { bool gptj_model_load(const std::string &fname, gptj_model & model, gpt_vocab & vocab, size_t * mem_req = nullptr)
{
printf("%s: loading model from '%s' - please wait ...\n", __func__, fname.c_str()); printf("%s: loading model from '%s' - please wait ...\n", __func__, fname.c_str());
if(mem_req != nullptr) { if(mem_req != nullptr) {
*mem_req = 0; *mem_req = 0;
@ -667,7 +668,8 @@ GPTJ::GPTJ()
d_ptr->modelLoaded = false; d_ptr->modelLoaded = false;
} }
size_t GPTJ::requiredMem(const std::string &modelPath, int n_ctx, int ngl) { size_t GPTJ::requiredMem(const std::string &modelPath, int n_ctx, int ngl)
{
(void)n_ctx; (void)n_ctx;
(void)ngl; (void)ngl;
gptj_model dummy_model; gptj_model dummy_model;
@ -677,7 +679,8 @@ size_t GPTJ::requiredMem(const std::string &modelPath, int n_ctx, int ngl) {
return mem_req; return mem_req;
} }
bool GPTJ::loadModel(const std::string &modelPath, int n_ctx, int ngl) { bool GPTJ::loadModel(const std::string &modelPath, int n_ctx, int ngl)
{
(void)n_ctx; (void)n_ctx;
(void)ngl; (void)ngl;
d_ptr->modelLoaded = false; d_ptr->modelLoaded = false;
@ -698,7 +701,8 @@ bool GPTJ::loadModel(const std::string &modelPath, int n_ctx, int ngl) {
return true; return true;
} }
void GPTJ::setThreadCount(int32_t n_threads) { void GPTJ::setThreadCount(int32_t n_threads)
{
d_ptr->n_threads = n_threads; d_ptr->n_threads = n_threads;
} }
@ -780,7 +784,8 @@ const std::vector<LLModel::Token> &GPTJ::endTokens() const
return fres; return fres;
} }
const char *get_arch_name(gguf_context *ctx_gguf) { const char *get_arch_name(gguf_context *ctx_gguf)
{
const int kid = gguf_find_key(ctx_gguf, "general.architecture"); const int kid = gguf_find_key(ctx_gguf, "general.architecture");
if (kid == -1) if (kid == -1)
throw std::runtime_error("key not found in model: general.architecture"); throw std::runtime_error("key not found in model: general.architecture");
@ -799,19 +804,23 @@ const char *get_arch_name(gguf_context *ctx_gguf) {
#endif #endif
extern "C" { extern "C" {
DLL_EXPORT bool is_g4a_backend_model_implementation() { DLL_EXPORT bool is_g4a_backend_model_implementation()
{
return true; return true;
} }
DLL_EXPORT const char *get_model_type() { DLL_EXPORT const char *get_model_type()
{
return modelType_; return modelType_;
} }
DLL_EXPORT const char *get_build_variant() { DLL_EXPORT const char *get_build_variant()
{
return GGML_BUILD_VARIANT; return GGML_BUILD_VARIANT;
} }
DLL_EXPORT char *get_file_arch(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,
@ -832,11 +841,13 @@ DLL_EXPORT char *get_file_arch(const char *fname) {
return arch; return arch;
} }
DLL_EXPORT bool is_arch_supported(const char *arch) { DLL_EXPORT bool is_arch_supported(const char *arch)
{
return !strcmp(arch, "gptj"); return !strcmp(arch, "gptj");
} }
DLL_EXPORT LLModel *construct() { DLL_EXPORT LLModel *construct()
{
return new GPTJ; return new GPTJ;
} }
} }

View File

@ -84,16 +84,19 @@ static const std::vector<const char *> EMBEDDING_ARCHES {
"bert", "nomic-bert", "bert", "nomic-bert",
}; };
static bool is_embedding_arch(const std::string &arch) { static bool is_embedding_arch(const std::string &arch)
{
return std::find(EMBEDDING_ARCHES.begin(), EMBEDDING_ARCHES.end(), arch) < EMBEDDING_ARCHES.end(); return std::find(EMBEDDING_ARCHES.begin(), EMBEDDING_ARCHES.end(), arch) < EMBEDDING_ARCHES.end();
} }
static bool llama_verbose() { static bool llama_verbose()
{
const char* var = getenv("GPT4ALL_VERBOSE_LLAMACPP"); const char* var = getenv("GPT4ALL_VERBOSE_LLAMACPP");
return var && *var; return var && *var;
} }
static void llama_log_callback(enum ggml_log_level level, const char *text, void *userdata) { static void llama_log_callback(enum ggml_log_level level, const char *text, void *userdata)
{
(void)userdata; (void)userdata;
if (llama_verbose() || level <= GGML_LOG_LEVEL_ERROR) { if (llama_verbose() || level <= GGML_LOG_LEVEL_ERROR) {
fputs(text, stderr); fputs(text, stderr);
@ -147,7 +150,8 @@ static int llama_sample_top_p_top_k(
return llama_sample_token(ctx, &candidates_p); return llama_sample_token(ctx, &candidates_p);
} }
const char *get_arch_name(gguf_context *ctx_gguf) { const char *get_arch_name(gguf_context *ctx_gguf)
{
const int kid = gguf_find_key(ctx_gguf, "general.architecture"); const int kid = gguf_find_key(ctx_gguf, "general.architecture");
if (kid == -1) if (kid == -1)
throw std::runtime_error("key not found in model: general.architecture"); throw std::runtime_error("key not found in model: general.architecture");
@ -159,7 +163,8 @@ const char *get_arch_name(gguf_context *ctx_gguf) {
return gguf_get_val_str(ctx_gguf, kid); return gguf_get_val_str(ctx_gguf, kid);
} }
static gguf_context *load_gguf(const char *fname) { static gguf_context *load_gguf(const char *fname)
{
struct gguf_init_params params = { struct gguf_init_params params = {
/*.no_alloc = */ true, /*.no_alloc = */ true,
/*.ctx = */ nullptr, /*.ctx = */ nullptr,
@ -180,7 +185,8 @@ static gguf_context *load_gguf(const char *fname) {
return ctx; return ctx;
} }
static int32_t get_arch_key_u32(std::string const &modelPath, std::string const &archKey) { static int32_t get_arch_key_u32(std::string const &modelPath, std::string const &archKey)
{
int32_t value = -1; int32_t value = -1;
std::string arch; std::string arch;
@ -237,7 +243,8 @@ struct llama_file_hparams {
enum llama_ftype ftype = LLAMA_FTYPE_MOSTLY_F16; enum llama_ftype ftype = LLAMA_FTYPE_MOSTLY_F16;
}; };
size_t LLamaModel::requiredMem(const std::string &modelPath, int n_ctx, int ngl) { size_t LLamaModel::requiredMem(const std::string &modelPath, int n_ctx, int ngl)
{
// TODO(cebtenzzre): update to GGUF // TODO(cebtenzzre): update to GGUF
(void)ngl; // FIXME(cetenzzre): use this value (void)ngl; // FIXME(cetenzzre): use this value
auto fin = std::ifstream(modelPath, std::ios::binary); auto fin = std::ifstream(modelPath, std::ios::binary);
@ -261,7 +268,8 @@ size_t LLamaModel::requiredMem(const std::string &modelPath, int n_ctx, int ngl)
return filesize + est_kvcache_size; return filesize + est_kvcache_size;
} }
bool LLamaModel::isModelBlacklisted(const std::string &modelPath) const { bool LLamaModel::isModelBlacklisted(const std::string &modelPath) const
{
auto * ctx = load_gguf(modelPath.c_str()); auto * ctx = load_gguf(modelPath.c_str());
if (!ctx) { if (!ctx) {
std::cerr << __func__ << ": failed to load " << modelPath << "\n"; std::cerr << __func__ << ": failed to load " << modelPath << "\n";
@ -297,7 +305,8 @@ bool LLamaModel::isModelBlacklisted(const std::string &modelPath) const {
return res; return res;
} }
bool LLamaModel::isEmbeddingModel(const std::string &modelPath) const { bool LLamaModel::isEmbeddingModel(const std::string &modelPath) const
{
bool result = false; bool result = false;
std::string arch; std::string arch;
@ -453,12 +462,14 @@ bool LLamaModel::loadModel(const std::string &modelPath, int n_ctx, int ngl)
return true; return true;
} }
void LLamaModel::setThreadCount(int32_t n_threads) { void LLamaModel::setThreadCount(int32_t n_threads)
{
d_ptr->n_threads = n_threads; d_ptr->n_threads = n_threads;
llama_set_n_threads(d_ptr->ctx, n_threads, n_threads); llama_set_n_threads(d_ptr->ctx, n_threads, n_threads);
} }
int32_t LLamaModel::threadCount() const { int32_t LLamaModel::threadCount() const
{
return d_ptr->n_threads; return d_ptr->n_threads;
} }
@ -581,7 +592,8 @@ int32_t LLamaModel::layerCount(std::string const &modelPath) const
} }
#ifdef GGML_USE_VULKAN #ifdef GGML_USE_VULKAN
static const char *getVulkanVendorName(uint32_t vendorID) { static const char *getVulkanVendorName(uint32_t vendorID)
{
switch (vendorID) { switch (vendorID) {
case 0x10DE: return "nvidia"; case 0x10DE: return "nvidia";
case 0x1002: return "amd"; case 0x1002: return "amd";
@ -738,11 +750,13 @@ bool LLamaModel::usingGPUDevice() const
return hasDevice; return hasDevice;
} }
const char *LLamaModel::backendName() const { const char *LLamaModel::backendName() const
{
return d_ptr->backend_name; return d_ptr->backend_name;
} }
const char *LLamaModel::gpuDeviceName() const { const char *LLamaModel::gpuDeviceName() const
{
if (usingGPUDevice()) { if (usingGPUDevice()) {
#if defined(GGML_USE_KOMPUTE) || defined(GGML_USE_VULKAN) || defined(GGML_USE_CUDA) #if defined(GGML_USE_KOMPUTE) || defined(GGML_USE_VULKAN) || defined(GGML_USE_CUDA)
return d_ptr->deviceName.c_str(); return d_ptr->deviceName.c_str();
@ -768,13 +782,15 @@ void llama_batch_add(
batch.n_tokens++; batch.n_tokens++;
} }
static void batch_add_seq(llama_batch &batch, const std::vector<LLModel::Token> &tokens, int seq_id) { static void batch_add_seq(llama_batch &batch, const std::vector<LLModel::Token> &tokens, int seq_id)
{
for (unsigned i = 0; i < tokens.size(); i++) { for (unsigned i = 0; i < tokens.size(); i++) {
llama_batch_add(batch, tokens[i], i, { seq_id }, i == tokens.size() - 1); llama_batch_add(batch, tokens[i], i, { seq_id }, i == tokens.size() - 1);
} }
} }
size_t LLamaModel::embeddingSize() const { size_t LLamaModel::embeddingSize() const
{
return llama_n_embd(d_ptr->model); return llama_n_embd(d_ptr->model);
} }
@ -894,12 +910,14 @@ void LLamaModel::embed(
// MD5 hash of "nomic empty" // MD5 hash of "nomic empty"
static const char EMPTY_PLACEHOLDER[] = "24df574ea1c998de59d5be15e769658e"; static const char EMPTY_PLACEHOLDER[] = "24df574ea1c998de59d5be15e769658e";
auto product(double a) -> std::function<double(double)> { auto product(double a) -> std::function<double(double)>
{
return [a](double b) { return a * b; }; return [a](double b) { return a * b; };
} }
template <typename T> template <typename T>
double getL2NormScale(T *start, T *end) { double getL2NormScale(T *start, T *end)
{
double magnitude = std::sqrt(std::inner_product(start, end, start, 0.0)); double magnitude = std::sqrt(std::inner_product(start, end, start, 0.0));
return 1.0 / std::max(magnitude, 1e-12); return 1.0 / std::max(magnitude, 1e-12);
} }
@ -1107,19 +1125,23 @@ void LLamaModel::embedInternal(
#endif #endif
extern "C" { extern "C" {
DLL_EXPORT bool is_g4a_backend_model_implementation() { DLL_EXPORT bool is_g4a_backend_model_implementation()
{
return true; return true;
} }
DLL_EXPORT const char *get_model_type() { DLL_EXPORT const char *get_model_type()
{
return modelType_; return modelType_;
} }
DLL_EXPORT const char *get_build_variant() { DLL_EXPORT const char *get_build_variant()
{
return GGML_BUILD_VARIANT; return GGML_BUILD_VARIANT;
} }
DLL_EXPORT char *get_file_arch(const char *fname) { DLL_EXPORT char *get_file_arch(const char *fname)
{
char *arch = nullptr; char *arch = nullptr;
std::string archStr; std::string archStr;
@ -1144,11 +1166,13 @@ cleanup:
return arch; return arch;
} }
DLL_EXPORT bool is_arch_supported(const char *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(); return std::find(KNOWN_ARCHES.begin(), KNOWN_ARCHES.end(), std::string(arch)) < KNOWN_ARCHES.end();
} }
DLL_EXPORT LLModel *construct() { DLL_EXPORT LLModel *construct()
{
llama_log_set(llama_log_callback, nullptr); llama_log_set(llama_log_callback, nullptr);
return new LLamaModel; return new LLamaModel;
} }

View File

@ -92,17 +92,20 @@ LLModel::Implementation::Implementation(Implementation &&o)
o.m_dlhandle = nullptr; o.m_dlhandle = nullptr;
} }
LLModel::Implementation::~Implementation() { LLModel::Implementation::~Implementation()
{
delete m_dlhandle; delete m_dlhandle;
} }
static bool isImplementation(const Dlhandle &dl) { static bool isImplementation(const Dlhandle &dl)
{
return dl.get<bool(uint32_t)>("is_g4a_backend_model_implementation"); return dl.get<bool(uint32_t)>("is_g4a_backend_model_implementation");
} }
// Add the CUDA Toolkit to the DLL search path on Windows. // Add the CUDA Toolkit to the DLL search path on Windows.
// This is necessary for chat.exe to find CUDA when started from Qt Creator. // This is necessary for chat.exe to find CUDA when started from Qt Creator.
static void addCudaSearchPath() { static void addCudaSearchPath()
{
#ifdef _WIN32 #ifdef _WIN32
if (const auto *cudaPath = _wgetenv(L"CUDA_PATH")) { if (const auto *cudaPath = _wgetenv(L"CUDA_PATH")) {
auto libDir = std::wstring(cudaPath) + L"\\bin"; auto libDir = std::wstring(cudaPath) + L"\\bin";
@ -114,7 +117,8 @@ static void addCudaSearchPath() {
#endif #endif
} }
const std::vector<LLModel::Implementation> &LLModel::Implementation::implementationList() { const std::vector<LLModel::Implementation> &LLModel::Implementation::implementationList()
{
if (cpu_supports_avx() == 0) { if (cpu_supports_avx() == 0) {
throw std::runtime_error("CPU does not support AVX"); throw std::runtime_error("CPU does not support AVX");
} }
@ -169,14 +173,16 @@ const std::vector<LLModel::Implementation> &LLModel::Implementation::implementat
return *libs; return *libs;
} }
static std::string applyCPUVariant(const std::string &buildVariant) { static std::string applyCPUVariant(const std::string &buildVariant)
{
if (buildVariant != "metal" && cpu_supports_avx2() == 0) { if (buildVariant != "metal" && cpu_supports_avx2() == 0) {
return buildVariant + "-avxonly"; return buildVariant + "-avxonly";
} }
return buildVariant; return buildVariant;
} }
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; std::optional<std::string> archName;
for (const auto& i : implementationList()) { for (const auto& i : implementationList()) {
@ -200,7 +206,8 @@ const LLModel::Implementation* LLModel::Implementation::implementation(const cha
throw BadArchError(std::move(*archName)); throw BadArchError(std::move(*archName));
} }
LLModel *LLModel::Implementation::construct(const std::string &modelPath, const std::string &backend, int n_ctx) { LLModel *LLModel::Implementation::construct(const std::string &modelPath, const std::string &backend, int n_ctx)
{
std::vector<std::string> desiredBackends; std::vector<std::string> desiredBackends;
if (backend != "auto") { if (backend != "auto") {
desiredBackends.push_back(backend); desiredBackends.push_back(backend);
@ -240,7 +247,8 @@ LLModel *LLModel::Implementation::construct(const std::string &modelPath, const
throw MissingImplementationError("Could not find any implementations for backend: " + backend); throw MissingImplementationError("Could not find any implementations for backend: " + backend);
} }
LLModel *LLModel::Implementation::constructGlobalLlama(const std::optional<std::string> &backend) { LLModel *LLModel::Implementation::constructGlobalLlama(const std::optional<std::string> &backend)
{
static std::unordered_map<std::string, std::unique_ptr<LLModel>> implCache; static std::unordered_map<std::string, std::unique_ptr<LLModel>> implCache;
const std::vector<Implementation> *impls; const std::vector<Implementation> *impls;
@ -284,7 +292,8 @@ LLModel *LLModel::Implementation::constructGlobalLlama(const std::optional<std::
return nullptr; return nullptr;
} }
std::vector<LLModel::GPUDevice> LLModel::Implementation::availableGPUDevices(size_t memoryRequired) { std::vector<LLModel::GPUDevice> LLModel::Implementation::availableGPUDevices(size_t memoryRequired)
{
std::vector<LLModel::GPUDevice> devices; std::vector<LLModel::GPUDevice> devices;
#ifndef __APPLE__ #ifndef __APPLE__
static const std::string backends[] = {"kompute", "cuda"}; static const std::string backends[] = {"kompute", "cuda"};
@ -299,33 +308,40 @@ std::vector<LLModel::GPUDevice> LLModel::Implementation::availableGPUDevices(siz
return devices; return devices;
} }
int32_t LLModel::Implementation::maxContextLength(const std::string &modelPath) { int32_t LLModel::Implementation::maxContextLength(const std::string &modelPath)
{
auto *llama = constructGlobalLlama(); auto *llama = constructGlobalLlama();
return llama ? llama->maxContextLength(modelPath) : -1; return llama ? llama->maxContextLength(modelPath) : -1;
} }
int32_t LLModel::Implementation::layerCount(const std::string &modelPath) { int32_t LLModel::Implementation::layerCount(const std::string &modelPath)
{
auto *llama = constructGlobalLlama(); auto *llama = constructGlobalLlama();
return llama ? llama->layerCount(modelPath) : -1; return llama ? llama->layerCount(modelPath) : -1;
} }
bool LLModel::Implementation::isEmbeddingModel(const std::string &modelPath) { bool LLModel::Implementation::isEmbeddingModel(const std::string &modelPath)
{
auto *llama = constructGlobalLlama(); auto *llama = constructGlobalLlama();
return llama && llama->isEmbeddingModel(modelPath); return llama && llama->isEmbeddingModel(modelPath);
} }
void LLModel::Implementation::setImplementationsSearchPath(const std::string& path) { void LLModel::Implementation::setImplementationsSearchPath(const std::string& path)
{
s_implementations_search_path = path; s_implementations_search_path = path;
} }
const std::string& LLModel::Implementation::implementationsSearchPath() { const std::string& LLModel::Implementation::implementationsSearchPath()
{
return s_implementations_search_path; return s_implementations_search_path;
} }
bool LLModel::Implementation::hasSupportedCPU() { bool LLModel::Implementation::hasSupportedCPU()
{
return cpu_supports_avx() != 0; return cpu_supports_avx() != 0;
} }
int LLModel::Implementation::cpuSupportsAVX2() { int LLModel::Implementation::cpuSupportsAVX2()
{
return cpu_supports_avx2(); return cpu_supports_avx2();
} }

View File

@ -20,7 +20,8 @@ struct LLModelWrapper {
~LLModelWrapper() { delete llModel; } ~LLModelWrapper() { delete llModel; }
}; };
llmodel_model llmodel_model_create(const char *model_path) { llmodel_model llmodel_model_create(const char *model_path)
{
const char *error; const char *error;
auto fres = llmodel_model_create2(model_path, "auto", &error); auto fres = llmodel_model_create2(model_path, "auto", &error);
if (!fres) { if (!fres) {
@ -29,7 +30,8 @@ llmodel_model llmodel_model_create(const char *model_path) {
return fres; return fres;
} }
static void llmodel_set_error(const char **errptr, const char *message) { static void llmodel_set_error(const char **errptr, const char *message)
{
thread_local static std::string last_error_message; thread_local static std::string last_error_message;
if (errptr) { if (errptr) {
last_error_message = message; last_error_message = message;
@ -37,7 +39,8 @@ static void llmodel_set_error(const char **errptr, const char *message) {
} }
} }
llmodel_model llmodel_model_create2(const char *model_path, const char *backend, const char **error) { llmodel_model llmodel_model_create2(const char *model_path, const char *backend, const char **error)
{
LLModel *llModel; LLModel *llModel;
try { try {
llModel = LLModel::Implementation::construct(model_path, backend); llModel = LLModel::Implementation::construct(model_path, backend);
@ -51,7 +54,8 @@ llmodel_model llmodel_model_create2(const char *model_path, const char *backend,
return wrapper; return wrapper;
} }
void llmodel_model_destroy(llmodel_model model) { void llmodel_model_destroy(llmodel_model model)
{
delete static_cast<LLModelWrapper *>(model); delete static_cast<LLModelWrapper *>(model);
} }

View File

@ -14,7 +14,8 @@
#include <vector> #include <vector>
// TODO(cebtenzzre): replace this with llama_kv_cache_seq_shift for llamamodel (GPT-J needs this as-is) // TODO(cebtenzzre): replace this with llama_kv_cache_seq_shift for llamamodel (GPT-J needs this as-is)
void LLModel::recalculateContext(PromptContext &promptCtx, std::function<bool(bool)> recalculate) { void LLModel::recalculateContext(PromptContext &promptCtx, std::function<bool(bool)> recalculate)
{
int n_keep = shouldAddBOS(); int n_keep = shouldAddBOS();
const int32_t n_discard = (promptCtx.n_ctx - n_keep) * promptCtx.contextErase; const int32_t n_discard = (promptCtx.n_ctx - n_keep) * promptCtx.contextErase;
@ -43,7 +44,8 @@ stop_generating:
recalculate(false); recalculate(false);
} }
static bool parsePromptTemplate(const std::string &tmpl, std::vector<std::smatch> &placeholders, std::string &err) { static bool parsePromptTemplate(const std::string &tmpl, std::vector<std::smatch> &placeholders, std::string &err)
{
static const std::regex placeholderRegex(R"(%[1-2](?![0-9]))"); static const std::regex placeholderRegex(R"(%[1-2](?![0-9]))");
auto it = std::sregex_iterator(tmpl.begin(), tmpl.end(), placeholderRegex); auto it = std::sregex_iterator(tmpl.begin(), tmpl.end(), placeholderRegex);

View File

@ -38,7 +38,8 @@ struct llm_kv_cache {
} }
}; };
inline void ggml_graph_compute_g4a(llm_buffer& buf, ggml_cgraph * graph, int n_threads) { inline void ggml_graph_compute_g4a(llm_buffer& buf, ggml_cgraph * graph, int n_threads)
{
struct ggml_cplan plan = ggml_graph_plan(graph, n_threads); struct ggml_cplan plan = ggml_graph_plan(graph, n_threads);
if (plan.work_size > 0) { if (plan.work_size > 0) {
buf.resize(plan.work_size); buf.resize(plan.work_size);

View File

@ -8,7 +8,8 @@
#include <regex> #include <regex>
#include <utility> #include <utility>
void replace(std::string & str, const std::string & needle, const std::string & replacement) { void replace(std::string & str, const std::string & needle, const std::string & replacement)
{
size_t pos = 0; size_t pos = 0;
while ((pos = str.find(needle, pos)) != std::string::npos) { while ((pos = str.find(needle, pos)) != std::string::npos) {
str.replace(pos, needle.length(), replacement); str.replace(pos, needle.length(), replacement);
@ -16,7 +17,8 @@ void replace(std::string & str, const std::string & needle, const std::string &
} }
} }
std::map<std::string, int32_t> json_parse(const std::string & fname) { std::map<std::string, int32_t> json_parse(const std::string & fname)
{
std::map<std::string, int32_t> result; std::map<std::string, int32_t> result;
// read file into string // read file into string
@ -107,7 +109,8 @@ std::map<std::string, int32_t> json_parse(const std::string & fname) {
return result; return result;
} }
std::vector<gpt_vocab::id> gpt_tokenize_inner(const gpt_vocab & vocab, const std::string & text) { std::vector<gpt_vocab::id> gpt_tokenize_inner(const gpt_vocab & vocab, const std::string & text)
{
std::vector<std::string> words; std::vector<std::string> words;
// first split the text into words // first split the text into words
@ -162,12 +165,14 @@ std::vector<gpt_vocab::id> gpt_tokenize_inner(const gpt_vocab & vocab, const std
return tokens; return tokens;
} }
std::string regex_escape(const std::string &s) { std::string regex_escape(const std::string &s)
{
static const std::regex metacharacters(R"([\.\^\$\-\+\(\)\[\]\{\}\|\?\*])"); static const std::regex metacharacters(R"([\.\^\$\-\+\(\)\[\]\{\}\|\?\*])");
return std::regex_replace(s, metacharacters, "\\$&"); return std::regex_replace(s, metacharacters, "\\$&");
} }
std::vector<gpt_vocab::id> gpt_tokenize(const gpt_vocab & vocab, const std::string & text) { std::vector<gpt_vocab::id> gpt_tokenize(const gpt_vocab & vocab, const std::string & text)
{
// Generate the subpattern from the special_tokens vector if it's not empty // Generate the subpattern from the special_tokens vector if it's not empty
if (!vocab.special_tokens.empty()) { if (!vocab.special_tokens.empty()) {
std::vector<gpt_vocab::id> out; std::vector<gpt_vocab::id> out;
@ -203,7 +208,8 @@ std::vector<gpt_vocab::id> gpt_tokenize(const gpt_vocab & vocab, const std::stri
} }
bool gpt_vocab_init(const std::string & fname, gpt_vocab & vocab) { bool gpt_vocab_init(const std::string & fname, gpt_vocab & vocab)
{
printf("%s: loading vocab from '%s'\n", __func__, fname.c_str()); printf("%s: loading vocab from '%s'\n", __func__, fname.c_str());
vocab.token_to_id = ::json_parse(fname); vocab.token_to_id = ::json_parse(fname);

View File

@ -14,7 +14,8 @@
// //
// General purpose inline functions // General purpose inline functions
// //
constexpr inline unsigned long long operator ""_MiB(unsigned long long bytes) { constexpr inline unsigned long long operator ""_MiB(unsigned long long bytes)
{
return bytes*1024*1024; return bytes*1024*1024;
} }

View File

@ -16,15 +16,16 @@ if(APPLE)
endif() endif()
endif() endif()
set(APP_VERSION_MAJOR 2) set(APP_VERSION_MAJOR 3)
set(APP_VERSION_MINOR 8) set(APP_VERSION_MINOR 0)
set(APP_VERSION_PATCH 1) set(APP_VERSION_PATCH 0)
set(APP_VERSION "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}") set(APP_VERSION_BASE "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}")
set(APP_VERSION "${APP_VERSION_BASE}-rc1")
# Include the binary directory for the generated header file # Include the binary directory for the generated header file
include_directories("${CMAKE_CURRENT_BINARY_DIR}") include_directories("${CMAKE_CURRENT_BINARY_DIR}")
project(gpt4all VERSION ${APP_VERSION} LANGUAGES CXX C) project(gpt4all VERSION ${APP_VERSION_BASE} LANGUAGES CXX C)
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON) set(CMAKE_AUTORCC ON)
@ -91,7 +92,6 @@ qt_add_executable(chat
chatmodel.h chatlistmodel.h chatlistmodel.cpp chatmodel.h chatlistmodel.h chatlistmodel.cpp
chatapi.h chatapi.cpp chatapi.h chatapi.cpp
database.h database.cpp database.h database.cpp
embeddings.h embeddings.cpp
download.h download.cpp download.h download.cpp
embllm.cpp embllm.h embllm.cpp embllm.h
localdocs.h localdocs.cpp localdocsmodel.h localdocsmodel.cpp localdocs.h localdocs.cpp localdocsmodel.h localdocsmodel.cpp
@ -102,6 +102,7 @@ qt_add_executable(chat
server.h server.cpp server.h server.cpp
logger.h logger.cpp logger.h logger.cpp
responsetext.h responsetext.cpp responsetext.h responsetext.cpp
oscompat.h oscompat.cpp
${METAL_SHADER_FILE} ${METAL_SHADER_FILE}
${APP_ICON_RESOURCE} ${APP_ICON_RESOURCE}
) )
@ -112,21 +113,24 @@ qt_add_qml_module(chat
NO_CACHEGEN NO_CACHEGEN
QML_FILES QML_FILES
main.qml main.qml
qml/AddCollectionView.qml
qml/AddModelView.qml
qml/ChatDrawer.qml qml/ChatDrawer.qml
qml/ChatView.qml qml/ChatView.qml
qml/CollectionsDialog.qml qml/CollectionsDrawer.qml
qml/ModelDownloaderDialog.qml qml/HomeView.qml
qml/ModelsView.qml
qml/NetworkDialog.qml qml/NetworkDialog.qml
qml/NewVersionDialog.qml qml/NewVersionDialog.qml
qml/ThumbsDownDialog.qml qml/ThumbsDownDialog.qml
qml/SettingsDialog.qml qml/SettingsView.qml
qml/StartupDialog.qml qml/StartupDialog.qml
qml/PopupDialog.qml qml/PopupDialog.qml
qml/AboutDialog.qml
qml/Theme.qml qml/Theme.qml
qml/ModelSettings.qml qml/ModelSettings.qml
qml/ApplicationSettings.qml qml/ApplicationSettings.qml
qml/LocalDocsSettings.qml qml/LocalDocsSettings.qml
qml/LocalDocsView.qml
qml/SwitchModelDialog.qml qml/SwitchModelDialog.qml
qml/MySettingsTab.qml qml/MySettingsTab.qml
qml/MySettingsStack.qml qml/MySettingsStack.qml
@ -138,33 +142,58 @@ qt_add_qml_module(chat
qml/MyComboBox.qml qml/MyComboBox.qml
qml/MyDialog.qml qml/MyDialog.qml
qml/MyDirectoryField.qml qml/MyDirectoryField.qml
qml/MyFancyLink.qml
qml/MyTextArea.qml qml/MyTextArea.qml
qml/MyTextField.qml qml/MyTextField.qml
qml/MyCheckBox.qml qml/MyCheckBox.qml
qml/MyBusyIndicator.qml qml/MyBusyIndicator.qml
qml/MyMiniButton.qml qml/MyMiniButton.qml
qml/MyToolButton.qml qml/MyToolButton.qml
qml/MyWelcomeButton.qml
RESOURCES RESOURCES
icons/antenna_1.svg
icons/antenna_2.svg
icons/antenna_3.svg
icons/send_message.svg icons/send_message.svg
icons/stop_generating.svg icons/stop_generating.svg
icons/regenerate.svg icons/regenerate.svg
icons/chat.svg
icons/changelog.svg
icons/close.svg icons/close.svg
icons/copy.svg icons/copy.svg
icons/db.svg icons/db.svg
icons/discord.svg
icons/download.svg icons/download.svg
icons/settings.svg icons/settings.svg
icons/eject.svg icons/eject.svg
icons/edit.svg icons/edit.svg
icons/email.svg
icons/file.svg
icons/file-md.svg
icons/file-pdf.svg
icons/file-txt.svg
icons/github.svg
icons/globe.svg
icons/home.svg
icons/image.svg icons/image.svg
icons/info.svg
icons/local-docs.svg
icons/models.svg
icons/nomic_logo.svg
icons/notes.svg
icons/search.svg
icons/trash.svg icons/trash.svg
icons/network.svg icons/network.svg
icons/thumbs_up.svg icons/thumbs_up.svg
icons/thumbs_down.svg icons/thumbs_down.svg
icons/twitter.svg
icons/left_panel_closed.svg icons/left_panel_closed.svg
icons/left_panel_open.svg icons/left_panel_open.svg
icons/logo.svg icons/logo.svg
icons/logo-32.png icons/logo-32.png
icons/logo-48.png icons/logo-48.png
icons/you.svg
icons/alt_logo.svg
) )
set_target_properties(chat PROPERTIES set_target_properties(chat PROPERTIES
@ -190,6 +219,13 @@ endif()
target_compile_definitions(chat target_compile_definitions(chat
PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>) PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>)
# usearch uses the identifier 'slots' which conflicts with Qt's 'slots' keyword
target_compile_definitions(chat PRIVATE QT_NO_SIGNALS_SLOTS_KEYWORDS)
target_include_directories(chat PRIVATE usearch/include
usearch/fp16/include)
if(LINUX) if(LINUX)
target_link_libraries(chat target_link_libraries(chat
PRIVATE Qt6::Quick Qt6::Svg Qt6::HttpServer Qt6::Sql Qt6::Pdf Qt6::WaylandCompositor) PRIVATE Qt6::Quick Qt6::Svg Qt6::HttpServer Qt6::Sql Qt6::Pdf Qt6::WaylandCompositor)
@ -200,6 +236,20 @@ endif()
target_link_libraries(chat target_link_libraries(chat
PRIVATE llmodel) PRIVATE llmodel)
# -- extra resources --
set(LOCAL_EMBEDDING_MODEL "nomic-embed-text-v1.5.f16.gguf")
set(LOCAL_EMBEDDING_MODEL_MD5 "a5401e7f7e46ed9fcaed5b60a281d547")
file(DOWNLOAD
"https://gpt4all.io/models/gguf/${LOCAL_EMBEDDING_MODEL}"
"${CMAKE_BINARY_DIR}/resources/${LOCAL_EMBEDDING_MODEL}"
EXPECTED_HASH "MD5=${LOCAL_EMBEDDING_MODEL_MD5}"
)
# -- install --
set(COMPONENT_NAME_MAIN ${PROJECT_NAME}) set(COMPONENT_NAME_MAIN ${PROJECT_NAME})
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
@ -264,6 +314,10 @@ if (LLMODEL_CUDA)
endif() endif()
endif() endif()
install(FILES "${CMAKE_BINARY_DIR}/resources/${LOCAL_EMBEDDING_MODEL}"
DESTINATION resources
COMPONENT ${COMPONENT_NAME_MAIN})
set(CPACK_GENERATOR "IFW") set(CPACK_GENERATOR "IFW")
set(CPACK_VERBATIM_VARIABLES YES) set(CPACK_VERBATIM_VARIABLES YES)
set(CPACK_IFW_VERBOSE ON) set(CPACK_IFW_VERBOSE ON)

View File

@ -15,6 +15,7 @@
#include <QTextStream> #include <QTextStream>
#include <Qt> #include <Qt>
#include <QtGlobal> #include <QtGlobal>
#include <QtLogging>
#include <utility> #include <utility>
@ -130,7 +131,7 @@ void Chat::prompt(const QString &prompt)
void Chat::regenerateResponse() void Chat::regenerateResponse()
{ {
const int index = m_chatModel->count() - 1; const int index = m_chatModel->count() - 1;
m_chatModel->updateReferences(index, QString(), QList<QString>()); m_chatModel->updateSources(index, QList<ResultInfo>());
emit regenerateResponseRequested(); emit regenerateResponseRequested();
} }
@ -193,43 +194,6 @@ void Chat::responseStopped(qint64 promptResponseMs)
{ {
m_tokenSpeed = QString(); m_tokenSpeed = QString();
emit tokenSpeedChanged(); emit tokenSpeedChanged();
const QString chatResponse = response();
QList<QString> references;
QList<QString> referencesContext;
int validReferenceNumber = 1;
for (const ResultInfo &info : databaseResults()) {
if (info.file.isEmpty())
continue;
if (validReferenceNumber == 1)
references.append((!chatResponse.endsWith("\n") ? "\n" : QString()) + QStringLiteral("\n---"));
QString reference;
{
QTextStream stream(&reference);
stream << (validReferenceNumber++) << ". ";
if (!info.title.isEmpty())
stream << "\"" << info.title << "\". ";
if (!info.author.isEmpty())
stream << "By " << info.author << ". ";
if (!info.date.isEmpty())
stream << "Date: " << info.date << ". ";
stream << "In " << info.file << ". ";
if (info.page != -1)
stream << "Page " << info.page << ". ";
if (info.from != -1) {
stream << "Lines " << info.from;
if (info.to != -1)
stream << "-" << info.to;
stream << ". ";
}
stream << "[Context](context://" << validReferenceNumber - 1 << ")";
}
references.append(reference);
referencesContext.append(info.text);
}
const int index = m_chatModel->count() - 1;
m_chatModel->updateReferences(index, references.join("\n"), referencesContext);
emit responseChanged(); emit responseChanged();
m_responseInProgress = false; m_responseInProgress = false;
@ -336,7 +300,7 @@ void Chat::generatedNameChanged(const QString &name)
// Only use the first three words maximum and remove newlines and extra spaces // Only use the first three words maximum and remove newlines and extra spaces
m_generatedName = name.simplified(); m_generatedName = name.simplified();
QStringList words = m_generatedName.split(' ', Qt::SkipEmptyParts); QStringList words = m_generatedName.split(' ', Qt::SkipEmptyParts);
int wordCount = qMin(3, words.size()); int wordCount = qMin(7, words.size());
m_name = words.mid(0, wordCount).join(' '); m_name = words.mid(0, wordCount).join(' ');
emit nameChanged(); emit nameChanged();
} }
@ -378,6 +342,8 @@ void Chat::handleFallbackReasonChanged(const QString &fallbackReason)
void Chat::handleDatabaseResultsChanged(const QList<ResultInfo> &results) void Chat::handleDatabaseResultsChanged(const QList<ResultInfo> &results)
{ {
m_databaseResults = results; m_databaseResults = results;
const int index = m_chatModel->count() - 1;
m_chatModel->updateSources(index, m_databaseResults);
} }
void Chat::handleModelInfoChanged(const ModelInfo &modelInfo) void Chat::handleModelInfoChanged(const ModelInfo &modelInfo)
@ -389,7 +355,8 @@ void Chat::handleModelInfoChanged(const ModelInfo &modelInfo)
emit modelInfoChanged(); emit modelInfoChanged();
} }
void Chat::handleTrySwitchContextOfLoadedModelCompleted(int value) { void Chat::handleTrySwitchContextOfLoadedModelCompleted(int value)
{
m_trySwitchContextInProgress = value; m_trySwitchContextInProgress = value;
emit trySwitchContextInProgressChanged(); emit trySwitchContextInProgressChanged();
} }

View File

@ -95,7 +95,7 @@ public:
void unloadAndDeleteLater(); void unloadAndDeleteLater();
void markForDeletion(); void markForDeletion();
qint64 creationDate() const { return m_creationDate; } QDateTime creationDate() const { return QDateTime::fromSecsSinceEpoch(m_creationDate); }
bool serialize(QDataStream &stream, int version) const; bool serialize(QDataStream &stream, int version) const;
bool deserialize(QDataStream &stream, int version); bool deserialize(QDataStream &stream, int version);
bool isServer() const { return m_isServer; } bool isServer() const { return m_isServer; }

View File

@ -8,6 +8,7 @@
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonValue>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QThread> #include <QThread>
@ -19,6 +20,8 @@
#include <iostream> #include <iostream>
using namespace Qt::Literals::StringLiterals;
//#define DEBUG //#define DEBUG
ChatAPI::ChatAPI() ChatAPI::ChatAPI()
@ -194,7 +197,7 @@ void ChatAPIWorker::request(const QString &apiKey,
m_ctx = promptCtx; m_ctx = promptCtx;
QUrl apiUrl(m_chat->url()); QUrl apiUrl(m_chat->url());
const QString authorization = QString("Bearer %1").arg(apiKey).trimmed(); const QString authorization = u"Bearer %1"_s.arg(apiKey).trimmed();
QNetworkRequest request(apiUrl); QNetworkRequest request(apiUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", authorization.toUtf8()); request.setRawHeader("Authorization", authorization.toUtf8());
@ -241,8 +244,8 @@ void ChatAPIWorker::handleReadyRead()
if (!ok || code != 200) { if (!ok || code != 200) {
m_chat->callResponse( m_chat->callResponse(
-1, -1,
QString("ERROR: ChatAPIWorker::handleReadyRead got HTTP Error %1 %2: %3") u"ERROR: ChatAPIWorker::handleReadyRead got HTTP Error %1 %2: %3"_s
.arg(code).arg(reply->errorString()).arg(reply->readAll()).toStdString() .arg(code).arg(reply->errorString(), reply->readAll()).toStdString()
); );
emit finished(); emit finished();
return; return;
@ -263,7 +266,7 @@ void ChatAPIWorker::handleReadyRead()
QJsonParseError err; QJsonParseError err;
const QJsonDocument document = QJsonDocument::fromJson(jsonData.toUtf8(), &err); const QJsonDocument document = QJsonDocument::fromJson(jsonData.toUtf8(), &err);
if (err.error != QJsonParseError::NoError) { if (err.error != QJsonParseError::NoError) {
m_chat->callResponse(-1, QString("ERROR: ChatAPI responded with invalid json \"%1\"") m_chat->callResponse(-1, u"ERROR: ChatAPI responded with invalid json \"%1\""_s
.arg(err.errorString()).toStdString()); .arg(err.errorString()).toStdString());
continue; continue;
} }

View File

@ -13,12 +13,13 @@
#include <QIODevice> #include <QIODevice>
#include <QSettings> #include <QSettings>
#include <QString> #include <QString>
#include <QStringList>
#include <Qt> #include <Qt>
#include <algorithm> #include <algorithm>
#define CHAT_FORMAT_MAGIC 0xF5D553CC #define CHAT_FORMAT_MAGIC 0xF5D553CC
#define CHAT_FORMAT_VERSION 7 #define CHAT_FORMAT_VERSION 8
class MyChatListModel: public ChatListModel { }; class MyChatListModel: public ChatListModel { };
Q_GLOBAL_STATIC(MyChatListModel, chatListModelInstance) Q_GLOBAL_STATIC(MyChatListModel, chatListModelInstance)
@ -64,7 +65,6 @@ ChatSaver::ChatSaver()
void ChatListModel::saveChats() void ChatListModel::saveChats()
{ {
const QString savePath = MySettings::globalInstance()->modelPath();
QVector<Chat*> toSave; QVector<Chat*> toSave;
for (Chat *chat : m_chats) { for (Chat *chat : m_chats) {
if (chat == m_serverChat) if (chat == m_serverChat)

View File

@ -56,7 +56,8 @@ public:
enum Roles { enum Roles {
IdRole = Qt::UserRole + 1, IdRole = Qt::UserRole + 1,
NameRole NameRole,
SectionRole
}; };
int rowCount(const QModelIndex &parent = QModelIndex()) const override int rowCount(const QModelIndex &parent = QModelIndex()) const override
@ -76,6 +77,26 @@ public:
return item->id(); return item->id();
case NameRole: case NameRole:
return item->name(); return item->name();
case SectionRole: {
if (item == m_serverChat)
return QString();
const QDate date = QDate::currentDate();
const QDate itemDate = item->creationDate().date();
if (date == itemDate)
return tr("TODAY");
else if (itemDate >= date.addDays(-7))
return tr("THIS WEEK");
else if (itemDate >= date.addMonths(-1))
return tr("THIS MONTH");
else if (itemDate >= date.addMonths(-6))
return tr("LAST SIX MONTHS");
else if (itemDate.year() == date.year())
return tr("THIS YEAR");
else if (itemDate.year() == date.year() - 1)
return tr("LAST YEAR");
else
return QString::number(itemDate.year());
}
} }
return QVariant(); return QVariant();
@ -86,6 +107,7 @@ public:
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[IdRole] = "id"; roles[IdRole] = "id";
roles[NameRole] = "name"; roles[NameRole] = "name";
roles[SectionRole] = "section";
return roles; return roles;
} }

View File

@ -16,6 +16,7 @@
#include <QMutex> #include <QMutex>
#include <QMutexLocker> #include <QMutexLocker>
#include <QSet> #include <QSet>
#include <QStringList>
#include <QVariantMap> #include <QVariantMap>
#include <QWaitCondition> #include <QWaitCondition>
#include <Qt> #include <Qt>
@ -28,9 +29,12 @@
#include <functional> #include <functional>
#include <limits> #include <limits>
#include <optional> #include <optional>
#include <string_view>
#include <utility> #include <utility>
#include <vector> #include <vector>
using namespace Qt::Literals::StringLiterals;
//#define DEBUG //#define DEBUG
//#define DEBUG_MODEL_LOADING //#define DEBUG_MODEL_LOADING
@ -180,7 +184,7 @@ bool ChatLLM::loadDefaultModel()
{ {
ModelInfo defaultModel = ModelList::globalInstance()->defaultModelInfo(); ModelInfo defaultModel = ModelList::globalInstance()->defaultModelInfo();
if (defaultModel.filename().isEmpty()) { if (defaultModel.filename().isEmpty()) {
emit modelLoadingError(QString("Could not find any model to load")); emit modelLoadingError(u"Could not find any model to load"_qs);
return false; return false;
} }
return loadModel(defaultModel); return loadModel(defaultModel);
@ -292,7 +296,7 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
setModelInfo(modelInfo); setModelInfo(modelInfo);
Q_ASSERT(!m_modelInfo.filename().isEmpty()); Q_ASSERT(!m_modelInfo.filename().isEmpty());
if (m_modelInfo.filename().isEmpty()) if (m_modelInfo.filename().isEmpty())
emit modelLoadingError(QString("Modelinfo is left null for %1").arg(modelInfo.filename())); emit modelLoadingError(u"Modelinfo is left null for %1"_s.arg(modelInfo.filename()));
else else
processSystemPrompt(); processSystemPrompt();
return true; return true;
@ -377,9 +381,9 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
static QSet<QString> warned; static QSet<QString> warned;
auto fname = modelInfo.filename(); auto fname = modelInfo.filename();
if (!warned.contains(fname)) { if (!warned.contains(fname)) {
emit modelLoadingWarning(QString( emit modelLoadingWarning(
"%1 is known to be broken. Please get a replacement via the download dialog." u"%1 is known to be broken. Please get a replacement via the download dialog."_s.arg(fname)
).arg(fname)); );
warned.insert(fname); // don't warn again until restart warned.insert(fname); // don't warn again until restart
} }
} }
@ -485,7 +489,7 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
if (!m_isServer) if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo)); LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo));
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(u"Could not load model due to invalid model file for %1"_s.arg(modelInfo.filename()));
modelLoadProps.insert("error", "loadmodel_failed"); modelLoadProps.insert("error", "loadmodel_failed");
} else { } else {
switch (m_llModelInfo.model->implementation().modelType()[0]) { switch (m_llModelInfo.model->implementation().modelType()[0]) {
@ -497,7 +501,7 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
if (!m_isServer) if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo)); LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo));
m_llModelInfo = LLModelInfo(); m_llModelInfo = LLModelInfo();
emit modelLoadingError(QString("Could not determine model type for %1").arg(modelInfo.filename())); emit modelLoadingError(u"Could not determine model type for %1"_s.arg(modelInfo.filename()));
} }
} }
@ -507,7 +511,7 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
if (!m_isServer) if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo)); LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo));
m_llModelInfo = LLModelInfo(); m_llModelInfo = LLModelInfo();
emit modelLoadingError(QString("Error loading %1: %2").arg(modelInfo.filename()).arg(constructError)); emit modelLoadingError(u"Error loading %1: %2"_s.arg(modelInfo.filename(), constructError));
} }
} }
#if defined(DEBUG_MODEL_LOADING) #if defined(DEBUG_MODEL_LOADING)
@ -527,7 +531,7 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
if (!m_isServer) if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo)); // release back into the store LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo)); // release back into the store
m_llModelInfo = LLModelInfo(); m_llModelInfo = LLModelInfo();
emit modelLoadingError(QString("Could not find file for model %1").arg(modelInfo.filename())); emit modelLoadingError(u"Could not find file for model %1"_s.arg(modelInfo.filename()));
} }
if (m_llModelInfo.model) { if (m_llModelInfo.model) {
@ -542,7 +546,8 @@ bool ChatLLM::isModelLoaded() const
return m_llModelInfo.model && m_llModelInfo.model->isModelLoaded(); return m_llModelInfo.model && m_llModelInfo.model->isModelLoaded();
} }
std::string remove_leading_whitespace(const std::string& input) { std::string remove_leading_whitespace(const std::string& input)
{
auto first_non_whitespace = std::find_if(input.begin(), input.end(), [](unsigned char c) { auto first_non_whitespace = std::find_if(input.begin(), input.end(), [](unsigned char c) {
return !std::isspace(c); return !std::isspace(c);
}); });
@ -553,7 +558,8 @@ std::string remove_leading_whitespace(const std::string& input) {
return std::string(first_non_whitespace, input.end()); return std::string(first_non_whitespace, input.end());
} }
std::string trim_whitespace(const std::string& input) { std::string trim_whitespace(const std::string& input)
{
auto first_non_whitespace = std::find_if(input.begin(), input.end(), [](unsigned char c) { auto first_non_whitespace = std::find_if(input.begin(), input.end(), [](unsigned char c) {
return !std::isspace(c); return !std::isspace(c);
}); });
@ -706,11 +712,15 @@ bool ChatLLM::promptInternal(const QList<QString> &collectionList, const QString
} }
// Augment the prompt template with the results if any // Augment the prompt template with the results if any
QList<QString> docsContext; QString docsContext;
if (!databaseResults.isEmpty()) if (!databaseResults.isEmpty()) {
docsContext.append("### Context:"); QStringList results;
for (const ResultInfo &info : databaseResults) for (const ResultInfo &info : databaseResults)
docsContext.append(info.text); results << u"Collection: %1\nPath: %2\nSnippet: %3"_s.arg(info.collection, info.path, info.text);
// FIXME(jared): use a Jinja prompt template instead of hardcoded Alpaca-style localdocs template
docsContext = u"### Context:\n%1\n\n"_s.arg(results.join("\n\n"));
}
int n_threads = MySettings::globalInstance()->threadCount(); int n_threads = MySettings::globalInstance()->threadCount();
@ -738,7 +748,7 @@ bool ChatLLM::promptInternal(const QList<QString> &collectionList, const QString
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
m_llModelInfo.model->prompt(docsContext.join("\n").toStdString(), "%1", promptFunc, responseFunc, recalcFunc, m_ctx); m_llModelInfo.model->prompt(docsContext.toStdString(), "%1", promptFunc, responseFunc, recalcFunc, m_ctx);
m_ctx.n_predict = old_n_predict; // now we are ready for a response m_ctx.n_predict = old_n_predict; // now we are ready for a response
} }
m_llModelInfo.model->prompt(prompt.toStdString(), promptTemplate.toStdString(), promptFunc, responseFunc, recalcFunc, m_ctx); m_llModelInfo.model->prompt(prompt.toStdString(), promptTemplate.toStdString(), promptFunc, responseFunc, recalcFunc, m_ctx);
@ -836,7 +846,7 @@ void ChatLLM::generateName()
auto responseFunc = std::bind(&ChatLLM::handleNameResponse, this, std::placeholders::_1, std::placeholders::_2); auto responseFunc = std::bind(&ChatLLM::handleNameResponse, this, std::placeholders::_1, std::placeholders::_2);
auto recalcFunc = std::bind(&ChatLLM::handleNameRecalculate, this, std::placeholders::_1); auto recalcFunc = std::bind(&ChatLLM::handleNameRecalculate, this, std::placeholders::_1);
LLModel::PromptContext ctx = m_ctx; LLModel::PromptContext ctx = m_ctx;
m_llModelInfo.model->prompt("Describe the above conversation in three words or less.", m_llModelInfo.model->prompt("Describe the above conversation in seven words or less.",
promptTemplate.toStdString(), promptFunc, responseFunc, recalcFunc, ctx); promptTemplate.toStdString(), promptFunc, responseFunc, recalcFunc, ctx);
std::string trimmed = trim_whitespace(m_nameResponse); std::string trimmed = trim_whitespace(m_nameResponse);
if (trimmed != m_nameResponse) { if (trimmed != m_nameResponse) {

View File

@ -22,6 +22,8 @@
#include <memory> #include <memory>
#include <string> #include <string>
using namespace Qt::Literals::StringLiterals;
class QDataStream; class QDataStream;
enum LLModelType { enum LLModelType {
@ -68,7 +70,7 @@ private Q_SLOTS:
void handleTimeout() void handleTimeout()
{ {
m_elapsed += m_time.restart(); m_elapsed += m_time.restart();
emit report(QString("%1 tokens/sec").arg(m_tokens / float(m_elapsed / 1000.0f), 0, 'g', 2)); emit report(u"%1 tokens/sec"_s.arg(m_tokens / float(m_elapsed / 1000.0f), 0, 'g', 2));
} }
private: private:

View File

@ -1,6 +1,8 @@
#ifndef CHATMODEL_H #ifndef CHATMODEL_H
#define CHATMODEL_H #define CHATMODEL_H
#include "database.h"
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QByteArray> #include <QByteArray>
#include <QDataStream> #include <QDataStream>
@ -26,17 +28,18 @@ struct ChatItem
Q_PROPERTY(bool stopped MEMBER stopped) Q_PROPERTY(bool stopped MEMBER stopped)
Q_PROPERTY(bool thumbsUpState MEMBER thumbsUpState) Q_PROPERTY(bool thumbsUpState MEMBER thumbsUpState)
Q_PROPERTY(bool thumbsDownState MEMBER thumbsDownState) Q_PROPERTY(bool thumbsDownState MEMBER thumbsDownState)
Q_PROPERTY(QString references MEMBER references) Q_PROPERTY(QList<ResultInfo> sources MEMBER sources)
Q_PROPERTY(QList<QString> referencesContext MEMBER referencesContext) Q_PROPERTY(QList<ResultInfo> consolidatedSources MEMBER consolidatedSources)
public: public:
// TODO: Maybe we should include the model name here as well as timestamp?
int id = 0; int id = 0;
QString name; QString name;
QString value; QString value;
QString prompt; QString prompt;
QString newResponse; QString newResponse;
QString references; QList<ResultInfo> sources;
QList<QString> referencesContext; QList<ResultInfo> consolidatedSources;
bool currentResponse = false; bool currentResponse = false;
bool stopped = false; bool stopped = false;
bool thumbsUpState = false; bool thumbsUpState = false;
@ -62,8 +65,8 @@ public:
StoppedRole, StoppedRole,
ThumbsUpStateRole, ThumbsUpStateRole,
ThumbsDownStateRole, ThumbsDownStateRole,
ReferencesRole, SourcesRole,
ReferencesContextRole ConsolidatedSourcesRole
}; };
int rowCount(const QModelIndex &parent = QModelIndex()) const override int rowCount(const QModelIndex &parent = QModelIndex()) const override
@ -97,10 +100,10 @@ public:
return item.thumbsUpState; return item.thumbsUpState;
case ThumbsDownStateRole: case ThumbsDownStateRole:
return item.thumbsDownState; return item.thumbsDownState;
case ReferencesRole: case SourcesRole:
return item.references; return QVariant::fromValue(item.sources);
case ReferencesContextRole: case ConsolidatedSourcesRole:
return item.referencesContext; return QVariant::fromValue(item.consolidatedSources);
} }
return QVariant(); return QVariant();
@ -118,8 +121,8 @@ public:
roles[StoppedRole] = "stopped"; roles[StoppedRole] = "stopped";
roles[ThumbsUpStateRole] = "thumbsUpState"; roles[ThumbsUpStateRole] = "thumbsUpState";
roles[ThumbsDownStateRole] = "thumbsDownState"; roles[ThumbsDownStateRole] = "thumbsDownState";
roles[ReferencesRole] = "references"; roles[SourcesRole] = "sources";
roles[ReferencesContextRole] = "referencesContext"; roles[ConsolidatedSourcesRole] = "consolidatedSources";
return roles; return roles;
} }
@ -196,19 +199,28 @@ public:
} }
} }
Q_INVOKABLE void updateReferences(int index, const QString &references, const QList<QString> &referencesContext) QList<ResultInfo> consolidateSources(const QList<ResultInfo> &sources) {
QMap<QString, ResultInfo> groupedData;
for (const ResultInfo &info : sources) {
if (groupedData.contains(info.file)) {
groupedData[info.file].text += "\n---\n" + info.text;
} else {
groupedData[info.file] = info;
}
}
QList<ResultInfo> consolidatedSources = groupedData.values();
return consolidatedSources;
}
Q_INVOKABLE void updateSources(int index, const QList<ResultInfo> &sources)
{ {
if (index < 0 || index >= m_chatItems.size()) return; if (index < 0 || index >= m_chatItems.size()) return;
ChatItem &item = m_chatItems[index]; ChatItem &item = m_chatItems[index];
if (item.references != references) { item.sources = sources;
item.references = references; item.consolidatedSources = consolidateSources(sources);
emit dataChanged(createIndex(index, 0), createIndex(index, 0), {ReferencesRole}); emit dataChanged(createIndex(index, 0), createIndex(index, 0), {SourcesRole});
} emit dataChanged(createIndex(index, 0), createIndex(index, 0), {ConsolidatedSourcesRole});
if (item.referencesContext != referencesContext) {
item.referencesContext = referencesContext;
emit dataChanged(createIndex(index, 0), createIndex(index, 0), {ReferencesContextRole});
}
} }
Q_INVOKABLE void updateThumbsUpState(int index, bool b) Q_INVOKABLE void updateThumbsUpState(int index, bool b)
@ -259,9 +271,56 @@ public:
stream << c.stopped; stream << c.stopped;
stream << c.thumbsUpState; stream << c.thumbsUpState;
stream << c.thumbsDownState; stream << c.thumbsDownState;
if (version > 2) { if (version > 7) {
stream << c.references; stream << c.sources.size();
stream << c.referencesContext; for (const ResultInfo &info : c.sources) {
Q_ASSERT(!info.file.isEmpty());
stream << info.collection;
stream << info.path;
stream << info.file;
stream << info.title;
stream << info.author;
stream << info.date;
stream << info.text;
stream << info.page;
stream << info.from;
stream << info.to;
}
} else if (version > 2) {
QList<QString> references;
QList<QString> referencesContext;
int validReferenceNumber = 1;
for (const ResultInfo &info : c.sources) {
if (info.file.isEmpty())
continue;
QString reference;
{
QTextStream stream(&reference);
stream << (validReferenceNumber++) << ". ";
if (!info.title.isEmpty())
stream << "\"" << info.title << "\". ";
if (!info.author.isEmpty())
stream << "By " << info.author << ". ";
if (!info.date.isEmpty())
stream << "Date: " << info.date << ". ";
stream << "In " << info.file << ". ";
if (info.page != -1)
stream << "Page " << info.page << ". ";
if (info.from != -1) {
stream << "Lines " << info.from;
if (info.to != -1)
stream << "-" << info.to;
stream << ". ";
}
stream << "[Context](context://" << validReferenceNumber - 1 << ")";
}
references.append(reference);
referencesContext.append(info.text);
}
stream << references.join("\n");
stream << referencesContext;
} }
} }
return stream.status() == QDataStream::Ok; return stream.status() == QDataStream::Ok;
@ -282,9 +341,109 @@ public:
stream >> c.stopped; stream >> c.stopped;
stream >> c.thumbsUpState; stream >> c.thumbsUpState;
stream >> c.thumbsDownState; stream >> c.thumbsDownState;
if (version > 2) { if (version > 7) {
stream >> c.references; qsizetype count;
stream >> c.referencesContext; stream >> count;
QList<ResultInfo> sources;
for (int i = 0; i < count; ++i) {
ResultInfo info;
stream >> info.collection;
stream >> info.path;
stream >> info.file;
stream >> info.title;
stream >> info.author;
stream >> info.date;
stream >> info.text;
stream >> info.page;
stream >> info.from;
stream >> info.to;
sources.append(info);
}
c.sources = sources;
c.consolidatedSources = consolidateSources(sources);
}else if (version > 2) {
QString references;
QList<QString> referencesContext;
stream >> references;
stream >> referencesContext;
if (!references.isEmpty()) {
QList<ResultInfo> sources;
QList<QString> referenceList = references.split("\n");
// Ignore empty lines and those that begin with "---" which is no longer used
for (auto it = referenceList.begin(); it != referenceList.end();) {
if (it->trimmed().isEmpty() || it->trimmed().startsWith("---"))
it = referenceList.erase(it);
else
++it;
}
Q_ASSERT(referenceList.size() == referencesContext.size());
for (int j = 0; j < referenceList.size(); ++j) {
QString reference = referenceList[j];
QString context = referencesContext[j];
ResultInfo info;
QTextStream refStream(&reference);
QString dummy;
int validReferenceNumber;
refStream >> validReferenceNumber >> dummy;
// Extract title (between quotes)
if (reference.contains("\"")) {
int startIndex = reference.indexOf('"') + 1;
int endIndex = reference.indexOf('"', startIndex);
info.title = reference.mid(startIndex, endIndex - startIndex);
}
// Extract author (after "By " and before the next period)
if (reference.contains("By ")) {
int startIndex = reference.indexOf("By ") + 3;
int endIndex = reference.indexOf('.', startIndex);
info.author = reference.mid(startIndex, endIndex - startIndex).trimmed();
}
// Extract date (after "Date: " and before the next period)
if (reference.contains("Date: ")) {
int startIndex = reference.indexOf("Date: ") + 6;
int endIndex = reference.indexOf('.', startIndex);
info.date = reference.mid(startIndex, endIndex - startIndex).trimmed();
}
// Extract file name (after "In " and before the "[Context]")
if (reference.contains("In ") && reference.contains(". [Context]")) {
int startIndex = reference.indexOf("In ") + 3;
int endIndex = reference.indexOf(". [Context]", startIndex);
info.file = reference.mid(startIndex, endIndex - startIndex).trimmed();
}
// Extract page number (after "Page " and before the next space)
if (reference.contains("Page ")) {
int startIndex = reference.indexOf("Page ") + 5;
int endIndex = reference.indexOf(' ', startIndex);
if (endIndex == -1) endIndex = reference.length();
info.page = reference.mid(startIndex, endIndex - startIndex).toInt();
}
// Extract lines (after "Lines " and before the next space or hyphen)
if (reference.contains("Lines ")) {
int startIndex = reference.indexOf("Lines ") + 6;
int endIndex = reference.indexOf(' ', startIndex);
if (endIndex == -1) endIndex = reference.length();
int hyphenIndex = reference.indexOf('-', startIndex);
if (hyphenIndex != -1 && hyphenIndex < endIndex) {
info.from = reference.mid(startIndex, hyphenIndex - startIndex).toInt();
info.to = reference.mid(hyphenIndex + 1, endIndex - hyphenIndex - 1).toInt();
} else {
info.from = reference.mid(startIndex, endIndex - startIndex).toInt();
}
}
info.text = context;
sources.append(info);
}
c.sources = sources;
c.consolidatedSources = consolidateSources(sources);
}
} }
beginInsertRows(QModelIndex(), m_chatItems.size(), m_chatItems.size()); beginInsertRows(QModelIndex(), m_chatItems.size(), m_chatItems.size());
m_chatItems.append(c); m_chatItems.append(c);

File diff suppressed because it is too large Load Diff

View File

@ -3,28 +3,39 @@
#include "embllm.h" // IWYU pragma: keep #include "embllm.h" // IWYU pragma: keep
#include <QElapsedTimer> #include <QDateTime>
#include <QFileInfo> #include <QFileInfo>
#include <QHash>
#include <QLatin1String> #include <QLatin1String>
#include <QList> #include <QList>
#include <QMap> #include <QMap>
#include <QObject> #include <QObject>
#include <QQueue> #include <QQueue>
#include <QSet>
#include <QSqlDatabase>
#include <QString> #include <QString>
#include <QStringList>
#include <QThread> #include <QThread>
#include <QVector> #include <QVector>
#include <QtGlobal>
#include <QtSql>
#include <cstddef> #include <cstddef>
class EmbeddingLLM; using namespace Qt::Literals::StringLiterals;
class Embeddings;
class QFileSystemWatcher; class QFileSystemWatcher;
class QSqlError; class QSqlError;
class QTextStream; class QTextStream;
class QTimer; class QTimer;
/* Version 0: GPT4All v2.4.3, full-text search
* Version 1: GPT4All v2.5.3, embeddings in hsnwlib
* Version 2: GPT4All v3.0.0, embeddings in sqlite */
// minimum supported version
static const int LOCALDOCS_MIN_VER = 1;
// current version
static const int LOCALDOCS_VERSION = 2;
struct DocumentInfo struct DocumentInfo
{ {
int folder; int folder;
@ -33,34 +44,82 @@ struct DocumentInfo
size_t currentPosition = 0; size_t currentPosition = 0;
bool currentlyProcessing = false; bool currentlyProcessing = false;
bool isPdf() const { bool isPdf() const {
return doc.suffix() == QLatin1String("pdf"); return doc.suffix() == u"pdf"_s;
} }
}; };
struct ResultInfo { struct ResultInfo {
QString file; // [Required] The name of the file, but not the full path QString collection; // [Required] The name of the collection
QString title; // [Optional] The title of the document QString path; // [Required] The full path
QString author; // [Optional] The author of the document QString file; // [Required] The name of the file, but not the full path
QString date; // [Required] The creation or the last modification date whichever is latest QString title; // [Optional] The title of the document
QString text; // [Required] The text actually used in the augmented context QString author; // [Optional] The author of the document
int page = -1; // [Optional] The page where the text was found QString date; // [Required] The creation or the last modification date whichever is latest
int from = -1; // [Optional] The line number where the text begins QString text; // [Required] The text actually used in the augmented context
int to = -1; // [Optional] The line number where the text ends int page = -1; // [Optional] The page where the text was found
int from = -1; // [Optional] The line number where the text begins
int to = -1; // [Optional] The line number where the text ends
bool operator==(const ResultInfo &other) const {
return file == other.file &&
title == other.title &&
author == other.author &&
date == other.date &&
text == other.text &&
page == other.page &&
from == other.from &&
to == other.to;
}
bool operator!=(const ResultInfo &other) const {
return !(*this == other);
}
Q_GADGET
Q_PROPERTY(QString collection MEMBER collection)
Q_PROPERTY(QString path MEMBER path)
Q_PROPERTY(QString file MEMBER file)
Q_PROPERTY(QString title MEMBER title)
Q_PROPERTY(QString author MEMBER author)
Q_PROPERTY(QString date MEMBER date)
Q_PROPERTY(QString text MEMBER text)
Q_PROPERTY(int page MEMBER page)
Q_PROPERTY(int from MEMBER from)
Q_PROPERTY(int to MEMBER to)
}; };
Q_DECLARE_METATYPE(ResultInfo)
struct CollectionItem { struct CollectionItem {
// -- Fields persisted to database --
int collection_id = -1;
int folder_id = -1;
QString collection; QString collection;
QString folder_path; QString folder_path;
int folder_id = -1; QString embeddingModel;
// -- Transient fields --
bool installed = false; bool installed = false;
bool indexing = false; bool indexing = false;
bool forceIndexing = false;
QString error; QString error;
// progress
int currentDocsToIndex = 0; int currentDocsToIndex = 0;
int totalDocsToIndex = 0; int totalDocsToIndex = 0;
size_t currentBytesToIndex = 0; size_t currentBytesToIndex = 0;
size_t totalBytesToIndex = 0; size_t totalBytesToIndex = 0;
size_t currentEmbeddingsToIndex = 0; size_t currentEmbeddingsToIndex = 0;
size_t totalEmbeddingsToIndex = 0; size_t totalEmbeddingsToIndex = 0;
// statistics
size_t totalDocs = 0;
size_t totalWords = 0;
size_t totalTokens = 0;
QDateTime startUpdate;
QDateTime lastUpdate;
QString fileCurrentlyProcessing;
}; };
Q_DECLARE_METATYPE(CollectionItem) Q_DECLARE_METATYPE(CollectionItem)
@ -68,53 +127,55 @@ class Database : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
Database(int chunkSize); Database(int chunkSize, QStringList extensions);
virtual ~Database(); ~Database() override;
bool isValid() const { return m_databaseValid; }
public Q_SLOTS: public Q_SLOTS:
void start(); void start();
void scanQueue(); void scanQueueBatch();
void scanDocuments(int folder_id, const QString &folder_path, bool isNew); void scanDocuments(int folder_id, const QString &folder_path);
bool addFolder(const QString &collection, const QString &path, bool fromDb); void forceIndexing(const QString &collection, const QString &embedding_model);
void forceRebuildFolder(const QString &path);
bool addFolder(const QString &collection, const QString &path, const QString &embedding_model);
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 changeChunkSize(int chunkSize); void changeChunkSize(int chunkSize);
void changeFileExtensions(const QStringList &extensions);
Q_SIGNALS: Q_SIGNALS:
void docsToScanChanged(); // Signals for the gui only
void updateInstalled(int folder_id, bool b); void requestUpdateGuiForCollectionItem(const CollectionItem &item);
void updateIndexing(int folder_id, bool b); void requestAddGuiCollectionItem(const CollectionItem &item);
void updateError(int folder_id, const QString &error); void requestRemoveGuiFolderById(const QString &collection, int folder_id);
void updateCurrentDocsToIndex(int folder_id, size_t currentDocsToIndex); void requestGuiCollectionListUpdated(const QList<CollectionItem> &collectionList);
void updateTotalDocsToIndex(int folder_id, size_t totalDocsToIndex); void databaseValidChanged();
void subtractCurrentBytesToIndex(int folder_id, size_t subtractedBytes);
void updateCurrentBytesToIndex(int folder_id, size_t currentBytesToIndex);
void updateTotalBytesToIndex(int folder_id, size_t totalBytesToIndex);
void updateCurrentEmbeddingsToIndex(int folder_id, size_t currentBytesToIndex);
void updateTotalEmbeddingsToIndex(int folder_id, size_t totalBytesToIndex);
void addCollectionItem(const CollectionItem &item, bool fromDb);
void removeFolderById(int folder_id);
void collectionListUpdated(const QList<CollectionItem> &collectionList);
private Q_SLOTS: private Q_SLOTS:
void directoryChanged(const QString &path); void directoryChanged(const QString &path);
bool addFolderToWatch(const QString &path); void addCurrentFolders();
bool removeFolderFromWatch(const QString &path);
int addCurrentFolders();
void handleEmbeddingsGenerated(const QVector<EmbeddingResult> &embeddings); void handleEmbeddingsGenerated(const QVector<EmbeddingResult> &embeddings);
void handleErrorGenerated(int folder_id, const QString &error); void handleErrorGenerated(const QVector<EmbeddingChunk> &chunks, const QString &error);
private: private:
enum class FolderStatus { Started, Embedding, Complete }; void transaction();
struct FolderStatusRecord { qint64 startTime; bool isNew; int numDocs, docsChanged, chunksRead; }; void commit();
void rollback();
void removeFolderInternal(const QString &collection, int folder_id, const QString &path); bool hasContent();
size_t chunkStream(QTextStream &stream, int folder_id, int document_id, const QString &file, // not found -> 0, , exists and has content -> 1, error -> -1
const QString &title, const QString &author, const QString &subject, const QString &keywords, int page, int openDatabase(const QString &modelPath, bool create = true, int ver = LOCALDOCS_VERSION);
int maxChunks = -1); bool openLatestDb(const QString &modelPath, QList<CollectionItem> &oldCollections);
void removeEmbeddingsByDocumentId(int document_id); bool initDb(const QString &modelPath, const QList<CollectionItem> &oldCollections);
void scheduleNext(int folder_id, size_t countForFolder); int checkAndAddFolderToDB(const QString &path);
bool removeFolderInternal(const QString &collection, int folder_id, const QString &path);
size_t chunkStream(QTextStream &stream, int folder_id, int document_id, const QString &embedding_model,
const QString &file, const QString &title, const QString &author, const QString &subject,
const QString &keywords, int page, int maxChunks = -1);
void appendChunk(const EmbeddingChunk &chunk);
void sendChunkList();
void updateFolderToIndex(int folder_id, size_t countForFolder, bool sendChunks = true);
void handleDocumentError(const QString &errorMessage, void handleDocumentError(const QString &errorMessage,
int document_id, const QString &document_path, const QSqlError &error); int document_id, const QString &document_path, const QSqlError &error);
size_t countOfDocuments(int folder_id) const; size_t countOfDocuments(int folder_id) const;
@ -123,20 +184,37 @@ 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 scanQueue();
void updateFolderStatus(int folder_id, FolderStatus status, int numDocs = -1, bool atStart = false, bool isNew = false); bool cleanDB();
void addFolderToWatch(const QString &path);
void removeFolderFromWatch(const QString &path);
QList<int> searchEmbeddings(const std::vector<float> &query, const QList<QString> &collections, int nNeighbors);
void setStartUpdateTime(CollectionItem &item);
void setLastUpdateTime(CollectionItem &item);
CollectionItem guiCollectionItem(int folder_id) const;
void updateGuiForCollectionItem(const CollectionItem &item);
void addGuiCollectionItem(const CollectionItem &item);
void removeGuiFolderById(const QString &collection, int folder_id);
void guiCollectionListUpdated(const QList<CollectionItem> &collectionList);
void scheduleUncompletedEmbeddings();
void updateCollectionStatistics();
private: private:
QSqlDatabase m_db;
int m_chunkSize; int m_chunkSize;
QStringList m_scannedFileExtensions;
QTimer *m_scanTimer; 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;
QSet<QString> m_watchedPaths;
EmbeddingLLM *m_embLLM; EmbeddingLLM *m_embLLM;
Embeddings *m_embeddings; QVector<EmbeddingChunk> m_chunkList;
QHash<int, CollectionItem> m_collectionMap; // used only for tracking indexing/embedding progress
std::atomic<bool> m_databaseValid;
}; };
#endif // DATABASE_H #endif // DATABASE_H

View File

@ -31,6 +31,8 @@
#include <cstddef> #include <cstddef>
#include <utility> #include <utility>
using namespace Qt::Literals::StringLiterals;
class MyDownload: public Download { }; class MyDownload: public Download { };
Q_GLOBAL_STATIC(MyDownload, downloadInstance) Q_GLOBAL_STATIC(MyDownload, downloadInstance)
Download *Download::globalInstance() Download *Download::globalInstance()
@ -48,15 +50,18 @@ Download::Download()
&Download::handleHashAndSaveFinished, Qt::QueuedConnection); &Download::handleHashAndSaveFinished, Qt::QueuedConnection);
connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this, connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this,
&Download::handleSslErrors); &Download::handleSslErrors);
updateLatestNews();
updateReleaseNotes(); updateReleaseNotes();
m_startTime = QDateTime::currentDateTime(); m_startTime = QDateTime::currentDateTime();
} }
static bool operator==(const ReleaseInfo& lhs, const ReleaseInfo& rhs) { static bool operator==(const ReleaseInfo& lhs, const ReleaseInfo& rhs)
{
return lhs.version == rhs.version; return lhs.version == rhs.version;
} }
static bool compareVersions(const QString &a, const QString &b) { static bool compareVersions(const QString &a, const QString &b)
{
QStringList aParts = a.split('.'); QStringList aParts = a.split('.');
QStringList bParts = b.split('.'); QStringList bParts = b.split('.');
@ -79,6 +84,8 @@ ReleaseInfo Download::releaseInfo() const
const QString currentVersion = QCoreApplication::applicationVersion(); const QString currentVersion = QCoreApplication::applicationVersion();
if (m_releaseMap.contains(currentVersion)) if (m_releaseMap.contains(currentVersion))
return m_releaseMap.value(currentVersion); return m_releaseMap.value(currentVersion);
if (!m_releaseMap.empty())
return m_releaseMap.last();
return ReleaseInfo(); return ReleaseInfo();
} }
@ -97,7 +104,6 @@ bool Download::isFirstStart(bool writeVersion) const
auto *mySettings = MySettings::globalInstance(); auto *mySettings = MySettings::globalInstance();
QSettings settings; QSettings settings;
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();
if (first && writeVersion) { if (first && writeVersion) {
@ -105,7 +111,6 @@ bool Download::isFirstStart(bool writeVersion) const
// let the user select these again // let the user select these again
settings.remove("network/usageStatsActive"); settings.remove("network/usageStatsActive");
settings.remove("network/isActive"); settings.remove("network/isActive");
settings.sync();
emit mySettings->networkUsageStatsActiveChanged(); emit mySettings->networkUsageStatsActiveChanged();
emit mySettings->networkIsActiveChanged(); emit mySettings->networkIsActiveChanged();
} }
@ -125,15 +130,26 @@ void Download::updateReleaseNotes()
connect(jsonReply, &QNetworkReply::finished, this, &Download::handleReleaseJsonDownloadFinished); connect(jsonReply, &QNetworkReply::finished, this, &Download::handleReleaseJsonDownloadFinished);
} }
void Download::updateLatestNews()
{
QUrl url("http://gpt4all.io/meta/latestnews.md");
QNetworkRequest request(url);
QSslConfiguration conf = request.sslConfiguration();
conf.setPeerVerifyMode(QSslSocket::VerifyNone);
request.setSslConfiguration(conf);
QNetworkReply *reply = m_networkManager.get(request);
connect(qGuiApp, &QCoreApplication::aboutToQuit, reply, &QNetworkReply::abort);
connect(reply, &QNetworkReply::finished, this, &Download::handleLatestNewsDownloadFinished);
}
void Download::downloadModel(const QString &modelFile) void Download::downloadModel(const QString &modelFile)
{ {
QFile *tempFile = new QFile(ModelList::globalInstance()->incompleteDownloadPath(modelFile)); QFile *tempFile = new QFile(ModelList::globalInstance()->incompleteDownloadPath(modelFile));
QDateTime modTime = tempFile->fileTime(QFile::FileModificationTime);
bool success = tempFile->open(QIODevice::WriteOnly | QIODevice::Append); bool success = tempFile->open(QIODevice::WriteOnly | QIODevice::Append);
qWarning() << "Opening temp file for writing:" << tempFile->fileName(); qWarning() << "Opening temp file for writing:" << tempFile->fileName();
if (!success) { if (!success) {
const QString error const QString error
= QString("ERROR: Could not open temp file: %1 %2").arg(tempFile->fileName()).arg(modelFile); = u"ERROR: Could not open temp file: %1 %2"_s.arg(tempFile->fileName(), modelFile);
qWarning() << error; qWarning() << error;
clearRetry(modelFile); clearRetry(modelFile);
ModelList::globalInstance()->updateDataByFilename(modelFile, {{ ModelList::DownloadErrorRole, error }}); ModelList::globalInstance()->updateDataByFilename(modelFile, {{ ModelList::DownloadErrorRole, error }});
@ -161,7 +177,7 @@ void Download::downloadModel(const QString &modelFile)
Network::globalInstance()->trackEvent("download_started", { {"model", 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", u"bytes=%1-"_s.arg(tempFile->pos()).toUtf8());
QSslConfiguration conf = request.sslConfiguration(); QSslConfiguration conf = request.sslConfiguration();
conf.setPeerVerifyMode(QSslSocket::VerifyNone); conf.setPeerVerifyMode(QSslSocket::VerifyNone);
request.setSslConfiguration(conf); request.setSslConfiguration(conf);
@ -176,8 +192,7 @@ void Download::downloadModel(const QString &modelFile)
void Download::cancelDownload(const QString &modelFile) void Download::cancelDownload(const QString &modelFile)
{ {
for (int i = 0; i < m_activeDownloads.size(); ++i) { for (auto [modelReply, tempFile]: m_activeDownloads.asKeyValueRange()) {
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()->trackEvent("download_canceled", { {"model", modelFile} }); Network::globalInstance()->trackEvent("download_canceled", { {"model", modelFile} });
@ -189,7 +204,6 @@ void Download::cancelDownload(const QString &modelFile)
modelReply->abort(); // Abort the download modelReply->abort(); // Abort the download
modelReply->deleteLater(); // Schedule the reply for deletion modelReply->deleteLater(); // Schedule the reply for deletion
QFile *tempFile = m_activeDownloads.value(modelReply);
tempFile->deleteLater(); tempFile->deleteLater();
m_activeDownloads.remove(modelReply); m_activeDownloads.remove(modelReply);
@ -308,6 +322,24 @@ void Download::parseReleaseJsonFile(const QByteArray &jsonData)
emit releaseInfoChanged(); emit releaseInfoChanged();
} }
void Download::handleLatestNewsDownloadFinished()
{
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
if (!reply)
return;
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "ERROR: network error occurred attempting to download latest news:" << reply->errorString();
reply->deleteLater();
return;
}
QByteArray responseData = reply->readAll();
m_latestNews = QString::fromUtf8(responseData);
reply->deleteLater();
emit latestNewsChanged();
}
bool Download::hasRetry(const QString &filename) const bool Download::hasRetry(const QString &filename) const
{ {
return m_activeRetries.contains(filename); return m_activeRetries.contains(filename);
@ -354,7 +386,7 @@ void Download::handleErrorOccurred(QNetworkReply::NetworkError code)
clearRetry(modelFilename); clearRetry(modelFilename);
const QString error const QString error
= QString("ERROR: Network error occurred attempting to download %1 code: %2 errorString %3") = u"ERROR: Network error occurred attempting to download %1 code: %2 errorString %3"_s
.arg(modelFilename) .arg(modelFilename)
.arg(code) .arg(code)
.arg(modelReply->errorString()); .arg(modelReply->errorString());
@ -428,7 +460,7 @@ void HashAndSaveFile::hashAndSave(const QString &expectedHash, QCryptographicHas
// Reopen the tempFile for hashing // Reopen the tempFile for hashing
if (!tempFile->open(QIODevice::ReadOnly)) { if (!tempFile->open(QIODevice::ReadOnly)) {
const QString error const QString error
= QString("ERROR: Could not open temp file for hashing: %1 %2").arg(tempFile->fileName()).arg(modelFilename); = u"ERROR: Could not open temp file for hashing: %1 %2"_s.arg(tempFile->fileName(), modelFilename);
qWarning() << error; qWarning() << error;
emit hashAndSaveFinished(false, error, tempFile, modelReply); emit hashAndSaveFinished(false, error, tempFile, modelReply);
return; return;
@ -440,10 +472,8 @@ void HashAndSaveFile::hashAndSave(const QString &expectedHash, QCryptographicHas
if (hash.result().toHex() != expectedHash.toLatin1()) { if (hash.result().toHex() != expectedHash.toLatin1()) {
tempFile->close(); tempFile->close();
const QString error const QString error
= QString("ERROR: Download error hash did not match: %1 != %2 for %3") = u"ERROR: Download error hash did not match: %1 != %2 for %3"_s
.arg(hash.result().toHex()) .arg(hash.result().toHex(), expectedHash.toLatin1(), modelFilename);
.arg(expectedHash.toLatin1())
.arg(modelFilename);
qWarning() << error; qWarning() << error;
tempFile->remove(); tempFile->remove();
emit hashAndSaveFinished(false, error, tempFile, modelReply); emit hashAndSaveFinished(false, error, tempFile, modelReply);
@ -464,7 +494,7 @@ void HashAndSaveFile::hashAndSave(const QString &expectedHash, QCryptographicHas
// Reopen the tempFile for copying // Reopen the tempFile for copying
if (!tempFile->open(QIODevice::ReadOnly)) { if (!tempFile->open(QIODevice::ReadOnly)) {
const QString error const QString error
= QString("ERROR: Could not open temp file at finish: %1 %2").arg(tempFile->fileName()).arg(modelFilename); = u"ERROR: Could not open temp file at finish: %1 %2"_s.arg(tempFile->fileName(), modelFilename);
qWarning() << error; qWarning() << error;
emit hashAndSaveFinished(false, error, tempFile, modelReply); emit hashAndSaveFinished(false, error, tempFile, modelReply);
return; return;
@ -484,7 +514,7 @@ void HashAndSaveFile::hashAndSave(const QString &expectedHash, QCryptographicHas
} else { } else {
QFile::FileError error = file.error(); QFile::FileError error = file.error();
const QString errorString const QString errorString
= QString("ERROR: Could not save model to location: %1 failed with code %1").arg(saveFilePath).arg(error); = u"ERROR: Could not save model to location: %1 failed with code %1"_s.arg(saveFilePath).arg(error);
qWarning() << errorString; qWarning() << errorString;
tempFile->close(); tempFile->close();
emit hashAndSaveFinished(false, errorString, tempFile, modelReply); emit hashAndSaveFinished(false, errorString, tempFile, modelReply);
@ -505,7 +535,7 @@ void Download::handleModelDownloadFinished()
if (modelReply->error()) { if (modelReply->error()) {
const QString errorString const QString errorString
= QString("ERROR: Downloading failed with code %1 \"%2\"").arg(modelReply->error()).arg(modelReply->errorString()); = u"ERROR: Downloading failed with code %1 \"%2\""_s.arg(modelReply->error()).arg(modelReply->errorString());
qWarning() << errorString; qWarning() << errorString;
modelReply->deleteLater(); modelReply->deleteLater();
tempFile->deleteLater(); tempFile->deleteLater();

View File

@ -52,12 +52,14 @@ class Download : public QObject
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool hasNewerRelease READ hasNewerRelease NOTIFY hasNewerReleaseChanged) Q_PROPERTY(bool hasNewerRelease READ hasNewerRelease NOTIFY hasNewerReleaseChanged)
Q_PROPERTY(ReleaseInfo releaseInfo READ releaseInfo NOTIFY releaseInfoChanged) Q_PROPERTY(ReleaseInfo releaseInfo READ releaseInfo NOTIFY releaseInfoChanged)
Q_PROPERTY(QString latestNews READ latestNews NOTIFY latestNewsChanged)
public: public:
static Download *globalInstance(); static Download *globalInstance();
ReleaseInfo releaseInfo() const; ReleaseInfo releaseInfo() const;
bool hasNewerRelease() const; bool hasNewerRelease() const;
QString latestNews() const { return m_latestNews; }
Q_INVOKABLE void downloadModel(const QString &modelFile); Q_INVOKABLE void downloadModel(const QString &modelFile);
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);
@ -65,11 +67,13 @@ public:
Q_INVOKABLE bool isFirstStart(bool writeVersion = false) const; Q_INVOKABLE bool isFirstStart(bool writeVersion = false) const;
public Q_SLOTS: public Q_SLOTS:
void updateLatestNews();
void updateReleaseNotes(); void updateReleaseNotes();
private Q_SLOTS: private Q_SLOTS:
void handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors); void handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
void handleReleaseJsonDownloadFinished(); void handleReleaseJsonDownloadFinished();
void handleLatestNewsDownloadFinished();
void handleErrorOccurred(QNetworkReply::NetworkError code); void handleErrorOccurred(QNetworkReply::NetworkError code);
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void handleModelDownloadFinished(); void handleModelDownloadFinished();
@ -82,6 +86,7 @@ Q_SIGNALS:
void hasNewerReleaseChanged(); void hasNewerReleaseChanged();
void requestHashAndSave(const QString &hash, QCryptographicHash::Algorithm a, const QString &saveFilePath, void requestHashAndSave(const QString &hash, QCryptographicHash::Algorithm a, const QString &saveFilePath,
QFile *tempFile, QNetworkReply *modelReply); QFile *tempFile, QNetworkReply *modelReply);
void latestNewsChanged();
private: private:
void parseReleaseJsonFile(const QByteArray &jsonData); void parseReleaseJsonFile(const QByteArray &jsonData);
@ -92,6 +97,7 @@ private:
HashAndSaveFile *m_hashAndSave; HashAndSaveFile *m_hashAndSave;
QMap<QString, ReleaseInfo> m_releaseMap; QMap<QString, ReleaseInfo> m_releaseMap;
QString m_latestNews;
QNetworkAccessManager m_networkManager; QNetworkAccessManager m_networkManager;
QMap<QNetworkReply*, QFile*> m_activeDownloads; QMap<QNetworkReply*, QFile*> m_activeDownloads;
QHash<QString, int> m_activeRetries; QHash<QString, int> m_activeRetries;

View File

@ -1,202 +0,0 @@
#include "embeddings.h"
#include "mysettings.h"
#include "hnswlib/hnswlib.h"
#include <QDebug>
#include <QFileInfo>
#include <QtGlobal>
#include <QtLogging>
#include <algorithm>
#include <atomic>
#include <exception>
#include <queue>
#include <string>
#include <utility>
#define EMBEDDINGS_VERSION 0
const int s_dim = 384; // Dimension of the elements
const int s_ef_construction = 200; // Controls index search speed/build speed tradeoff
const int s_M = 16; // Tightly connected with internal dimensionality of the data
// strongly affects the memory consumption
Embeddings::Embeddings(QObject *parent)
: QObject(parent)
, m_space(nullptr)
, m_hnsw(nullptr)
{
m_filePath = MySettings::globalInstance()->modelPath()
+ QString("embeddings_v%1.dat").arg(EMBEDDINGS_VERSION);
}
Embeddings::~Embeddings()
{
delete m_hnsw;
m_hnsw = nullptr;
delete m_space;
m_space = nullptr;
}
bool Embeddings::load()
{
QFileInfo info(m_filePath);
if (!info.exists()) {
qWarning() << "ERROR: loading embeddings file does not exist" << m_filePath;
return false;
}
if (!info.isReadable()) {
qWarning() << "ERROR: loading embeddings file is not readable" << m_filePath;
return false;
}
if (!info.isWritable()) {
qWarning() << "ERROR: loading embeddings file is not writeable" << m_filePath;
return false;
}
try {
m_space = new hnswlib::InnerProductSpace(s_dim);
m_hnsw = new hnswlib::HierarchicalNSW<float>(m_space, m_filePath.toStdString(), s_M, s_ef_construction);
} catch (const std::exception &e) {
qWarning() << "ERROR: could not load hnswlib index:" << e.what();
return false;
}
return isLoaded();
}
bool Embeddings::load(qint64 maxElements)
{
try {
m_space = new hnswlib::InnerProductSpace(s_dim);
m_hnsw = new hnswlib::HierarchicalNSW<float>(m_space, maxElements, s_M, s_ef_construction);
} catch (const std::exception &e) {
qWarning() << "ERROR: could not create hnswlib index:" << e.what();
return false;
}
return isLoaded();
}
bool Embeddings::save()
{
if (!isLoaded())
return false;
try {
m_hnsw->saveIndex(m_filePath.toStdString());
} catch (const std::exception &e) {
qWarning() << "ERROR: could not save hnswlib index:" << e.what();
return false;
}
return true;
}
bool Embeddings::isLoaded() const
{
return m_hnsw != nullptr;
}
bool Embeddings::fileExists() const
{
QFileInfo info(m_filePath);
return info.exists();
}
bool Embeddings::resize(qint64 size)
{
if (!isLoaded()) {
qWarning() << "ERROR: attempting to resize an embedding when the embeddings are not open!";
return false;
}
Q_ASSERT(m_hnsw);
try {
m_hnsw->resizeIndex(size);
} catch (const std::exception &e) {
qWarning() << "ERROR: could not resize hnswlib index:" << e.what();
return false;
}
return true;
}
bool Embeddings::add(const std::vector<float> &embedding, qint64 label)
{
if (!isLoaded()) {
bool success = load(500);
if (!success) {
qWarning() << "ERROR: attempting to add an embedding when the embeddings are not open!";
return false;
}
}
Q_ASSERT(m_hnsw);
if (m_hnsw->cur_element_count + 1 > m_hnsw->max_elements_) {
if (!resize(m_hnsw->max_elements_ + 500)) {
return false;
}
}
if (embedding.empty())
return false;
try {
m_hnsw->addPoint(embedding.data(), label, false);
} catch (const std::exception &e) {
qWarning() << "ERROR: could not add embedding to hnswlib index:" << e.what();
return false;
}
return true;
}
void Embeddings::remove(qint64 label)
{
if (!isLoaded()) {
qWarning() << "ERROR: attempting to remove an embedding when the embeddings are not open!";
return;
}
Q_ASSERT(m_hnsw);
try {
m_hnsw->markDelete(label);
} catch (const std::exception &e) {
qWarning() << "ERROR: could not add remove embedding from hnswlib index:" << e.what();
}
}
void Embeddings::clear()
{
delete m_hnsw;
m_hnsw = nullptr;
delete m_space;
m_space = nullptr;
}
std::vector<qint64> Embeddings::search(const std::vector<float> &embedding, int K)
{
if (!isLoaded())
return {};
Q_ASSERT(m_hnsw);
std::priority_queue<std::pair<float, hnswlib::labeltype>> result;
try {
result = m_hnsw->searchKnn(embedding.data(), K);
} catch (const std::exception &e) {
qWarning() << "ERROR: could not search hnswlib index:" << e.what();
return {};
}
std::vector<qint64> neighbors;
neighbors.reserve(K);
while(!result.empty()) {
neighbors.push_back(result.top().second);
result.pop();
}
// Reverse the neighbors, as the top of the priority queue is the farthest neighbor.
std::reverse(neighbors.begin(), neighbors.end());
return neighbors;
}

View File

@ -1,48 +0,0 @@
#ifndef EMBEDDINGS_H
#define EMBEDDINGS_H
#include <QObject>
#include <QString>
#include <QtGlobal>
#include <vector>
namespace hnswlib {
class InnerProductSpace;
template <typename T> class HierarchicalNSW;
}
class Embeddings : public QObject
{
Q_OBJECT
public:
Embeddings(QObject *parent);
virtual ~Embeddings();
bool load();
bool load(qint64 maxElements);
bool save();
bool isLoaded() const;
bool fileExists() const;
bool resize(qint64 size);
// Adds the embedding and returns the label used
bool add(const std::vector<float> &embedding, qint64 label);
// Removes the embedding at label by marking it as unused
void remove(qint64 label);
// Clears the embeddings
void clear();
// Performs a nearest neighbor search of the embeddings and returns a vector of labels
// for the K nearest neighbors of the given embedding
std::vector<qint64> search(const std::vector<float> &embedding, int K);
private:
QString m_filePath;
hnswlib::InnerProductSpace *m_space;
hnswlib::HierarchicalNSW<float> *m_hnsw;
};
#endif // EMBEDDINGS_H

View File

@ -1,6 +1,7 @@
#include "embllm.h" #include "embllm.h"
#include "modellist.h" #include "modellist.h"
#include "mysettings.h"
#include "../gpt4all-backend/llmodel.h" #include "../gpt4all-backend/llmodel.h"
@ -13,8 +14,8 @@
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonValue>
#include <QList> #include <QList>
#include <QMutexLocker>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkReply> #include <QNetworkReply>
#include <QNetworkRequest> #include <QNetworkRequest>
@ -24,16 +25,20 @@
#include <QtLogging> #include <QtLogging>
#include <exception> #include <exception>
#include <string>
#include <utility> #include <utility>
using namespace Qt::Literals::StringLiterals;
static const QString EMBEDDING_MODEL_NAME = u"nomic-embed-text-v1.5"_s;
static const QString LOCAL_EMBEDDING_MODEL = u"nomic-embed-text-v1.5.f16.gguf"_s;
EmbeddingLLMWorker::EmbeddingLLMWorker() EmbeddingLLMWorker::EmbeddingLLMWorker()
: QObject(nullptr) : QObject(nullptr)
, m_networkManager(new QNetworkAccessManager(this)) , m_networkManager(new QNetworkAccessManager(this))
, m_model(nullptr)
, m_stopGenerating(false) , m_stopGenerating(false)
{ {
moveToThread(&m_workerThread); moveToThread(&m_workerThread);
connect(this, &EmbeddingLLMWorker::requestAtlasQueryEmbedding, this, &EmbeddingLLMWorker::atlasQueryEmbeddingRequested);
connect(this, &EmbeddingLLMWorker::finished, &m_workerThread, &QThread::quit, Qt::DirectConnection); connect(this, &EmbeddingLLMWorker::finished, &m_workerThread, &QThread::quit, Qt::DirectConnection);
m_workerThread.setObjectName("embedding"); m_workerThread.setObjectName("embedding");
m_workerThread.start(); m_workerThread.start();
@ -58,44 +63,31 @@ void EmbeddingLLMWorker::wait()
bool EmbeddingLLMWorker::loadModel() bool EmbeddingLLMWorker::loadModel()
{ {
const EmbeddingModels *embeddingModels = ModelList::globalInstance()->installedEmbeddingModels(); m_nomicAPIKey.clear();
if (!embeddingModels->count()) m_model = nullptr;
return false;
const ModelInfo defaultModel = embeddingModels->defaultModelInfo(); if (MySettings::globalInstance()->localDocsUseRemoteEmbed()) {
m_nomicAPIKey = MySettings::globalInstance()->localDocsNomicAPIKey();
QString filePath = defaultModel.dirpath + defaultModel.filename(); return true;
QFileInfo fileInfo(filePath);
if (!fileInfo.exists()) {
qWarning() << "WARNING: Could not load sbert because file does not exist";
m_model = nullptr;
return false;
} }
auto filename = fileInfo.fileName(); QString filePath = u"%1/../resources/%2"_s.arg(QCoreApplication::applicationDirPath(), LOCAL_EMBEDDING_MODEL);
bool isNomic = filename.startsWith("gpt4all-nomic-") && filename.endsWith(".rmodel"); if (!QFileInfo::exists(filePath)) {
if (isNomic) { qWarning() << "WARNING: Local embedding model not found";
QFile file(filePath); return false;
if (!file.open(QIODeviceBase::ReadOnly)) {
qWarning() << "failed to open" << filePath << ":" << file.errorString();
m_model = nullptr;
return false;
}
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
QJsonObject obj = doc.object();
m_nomicAPIKey = obj["apiKey"].toString();
file.close();
return true;
} }
try { try {
m_model = LLModel::Implementation::construct(filePath.toStdString()); m_model = LLModel::Implementation::construct(filePath.toStdString());
} catch (const std::exception &e) { } catch (const std::exception &e) {
qWarning() << "WARNING: Could not load embedding model:" << e.what(); qWarning() << "WARNING: Could not load embedding model:" << e.what();
m_model = nullptr;
return false; return false;
} }
// FIXME(jared): the user may want this to take effect without having to restart
int n_threads = MySettings::globalInstance()->threadCount();
m_model->setThreadCount(n_threads);
// 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);
@ -115,31 +107,38 @@ bool EmbeddingLLMWorker::loadModel()
return true; return true;
} }
bool EmbeddingLLMWorker::hasModel() const std::vector<float> EmbeddingLLMWorker::generateQueryEmbedding(const QString &text)
{ {
return m_model || !m_nomicAPIKey.isEmpty(); {
} QMutexLocker locker(&m_mutex);
bool EmbeddingLLMWorker::isNomic() const if (!hasModel() && !loadModel()) {
{ qWarning() << "WARNING: Could not load model for embeddings";
return !m_nomicAPIKey.isEmpty(); return {};
} }
// this function is always called for retrieval tasks if (!isNomic()) {
std::vector<float> EmbeddingLLMWorker::generateSyncEmbedding(const QString &text) std::vector<float> embedding(m_model->embeddingSize());
{
Q_ASSERT(!isNomic()); try {
std::vector<float> embedding(m_model->embeddingSize()); m_model->embed({text.toStdString()}, embedding.data(), true);
try { } catch (const std::exception &e) {
m_model->embed({text.toStdString()}, embedding.data(), true); qWarning() << "WARNING: LLModel::embed failed:" << e.what();
} catch (const std::exception &e) { return {};
qWarning() << "WARNING: LLModel::embed failed: " << e.what(); }
return {};
return embedding;
}
} }
return embedding;
EmbeddingLLMWorker worker;
emit worker.requestAtlasQueryEmbedding(text);
worker.wait();
return worker.lastResponse();
} }
void EmbeddingLLMWorker::sendAtlasRequest(const QStringList &texts, const QString &taskType, QVariant userData) { void EmbeddingLLMWorker::sendAtlasRequest(const QStringList &texts, const QString &taskType, const QVariant &userData)
{
QJsonObject root; QJsonObject root;
root.insert("model", "nomic-embed-text-v1"); root.insert("model", "nomic-embed-text-v1");
root.insert("texts", QJsonArray::fromStringList(texts)); root.insert("texts", QJsonArray::fromStringList(texts));
@ -148,7 +147,7 @@ void EmbeddingLLMWorker::sendAtlasRequest(const QStringList &texts, const QStrin
QJsonDocument doc(root); QJsonDocument doc(root);
QUrl nomicUrl("https://api-atlas.nomic.ai/v1/embedding/text"); QUrl nomicUrl("https://api-atlas.nomic.ai/v1/embedding/text");
const QString authorization = QString("Bearer %1").arg(m_nomicAPIKey).trimmed(); const QString authorization = u"Bearer %1"_s.arg(m_nomicAPIKey).trimmed();
QNetworkRequest request(nomicUrl); QNetworkRequest request(nomicUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", authorization.toUtf8()); request.setRawHeader("Authorization", authorization.toUtf8());
@ -158,50 +157,63 @@ void EmbeddingLLMWorker::sendAtlasRequest(const QStringList &texts, const QStrin
connect(reply, &QNetworkReply::finished, this, &EmbeddingLLMWorker::handleFinished); connect(reply, &QNetworkReply::finished, this, &EmbeddingLLMWorker::handleFinished);
} }
// this function is always called for retrieval tasks void EmbeddingLLMWorker::atlasQueryEmbeddingRequested(const QString &text)
void EmbeddingLLMWorker::requestSyncEmbedding(const QString &text)
{ {
if (!hasModel() && !loadModel()) { {
qWarning() << "WARNING: Could not load model for embeddings"; QMutexLocker locker(&m_mutex);
return; if (!hasModel() && !loadModel()) {
} qWarning() << "WARNING: Could not load model for embeddings";
return;
}
if (!isNomic()) { if (!isNomic()) {
qWarning() << "WARNING: Request to generate sync embeddings for local model invalid"; qWarning() << "WARNING: Request to generate sync embeddings for local model invalid";
return; return;
} }
Q_ASSERT(hasModel()); Q_ASSERT(hasModel());
}
sendAtlasRequest({text}, "search_query"); sendAtlasRequest({text}, "search_query");
} }
// this function is always called for storage into the database void EmbeddingLLMWorker::docEmbeddingsRequested(const QVector<EmbeddingChunk> &chunks)
void EmbeddingLLMWorker::requestAsyncEmbedding(const QVector<EmbeddingChunk> &chunks)
{ {
if (m_stopGenerating) if (m_stopGenerating)
return; return;
if (!hasModel() && !loadModel()) { bool isNomic;
qWarning() << "WARNING: Could not load model for embeddings"; {
return; QMutexLocker locker(&m_mutex);
if (!hasModel() && !loadModel()) {
qWarning() << "WARNING: Could not load model for embeddings";
return;
}
isNomic = this->isNomic();
} }
if (m_nomicAPIKey.isEmpty()) { if (!isNomic) {
QVector<EmbeddingResult> results; QVector<EmbeddingResult> results;
results.reserve(chunks.size()); results.reserve(chunks.size());
for (auto c : chunks) { for (const auto &c: chunks) {
EmbeddingResult result; EmbeddingResult result;
result.model = c.model;
result.folder_id = c.folder_id; result.folder_id = c.folder_id;
result.chunk_id = c.chunk_id; result.chunk_id = c.chunk_id;
// TODO(cebtenzzre): take advantage of batched embeddings // TODO(cebtenzzre): take advantage of batched embeddings
result.embedding.resize(m_model->embeddingSize()); result.embedding.resize(m_model->embeddingSize());
try {
m_model->embed({c.chunk.toStdString()}, result.embedding.data(), false); {
} catch (const std::exception &e) { QMutexLocker locker(&m_mutex);
qWarning() << "WARNING: LLModel::embed failed:" << e.what(); try {
return; m_model->embed({c.chunk.toStdString()}, result.embedding.data(), false);
} catch (const std::exception &e) {
qWarning() << "WARNING: LLModel::embed failed:" << e.what();
return;
}
} }
results << result; results << result;
} }
emit embeddingsGenerated(results); emit embeddingsGenerated(results);
@ -214,14 +226,15 @@ void EmbeddingLLMWorker::requestAsyncEmbedding(const QVector<EmbeddingChunk> &ch
sendAtlasRequest(texts, "search_document", QVariant::fromValue(chunks)); sendAtlasRequest(texts, "search_document", QVariant::fromValue(chunks));
} }
std::vector<float> jsonArrayToVector(const QJsonArray &jsonArray) { std::vector<float> jsonArrayToVector(const QJsonArray &jsonArray)
{
std::vector<float> result; std::vector<float> result;
for (const QJsonValue &innerValue : jsonArray) { for (const auto &innerValue: jsonArray) {
if (innerValue.isArray()) { if (innerValue.isArray()) {
QJsonArray innerArray = innerValue.toArray(); QJsonArray innerArray = innerValue.toArray();
result.reserve(result.size() + innerArray.size()); result.reserve(result.size() + innerArray.size());
for (const QJsonValue &value : innerArray) { for (const auto &value: innerArray) {
result.push_back(static_cast<float>(value.toDouble())); result.push_back(static_cast<float>(value.toDouble()));
} }
} }
@ -230,7 +243,8 @@ std::vector<float> jsonArrayToVector(const QJsonArray &jsonArray) {
return result; return result;
} }
QVector<EmbeddingResult> jsonArrayToEmbeddingResults(const QVector<EmbeddingChunk>& chunks, const QJsonArray& embeddings) { QVector<EmbeddingResult> jsonArrayToEmbeddingResults(const QVector<EmbeddingChunk>& chunks, const QJsonArray& embeddings)
{
QVector<EmbeddingResult> results; QVector<EmbeddingResult> results;
if (chunks.size() != embeddings.size()) { if (chunks.size() != embeddings.size()) {
@ -243,10 +257,11 @@ QVector<EmbeddingResult> jsonArrayToEmbeddingResults(const QVector<EmbeddingChun
const QJsonArray embeddingArray = embeddings.at(i).toArray(); const QJsonArray embeddingArray = embeddings.at(i).toArray();
std::vector<float> embeddingVector; std::vector<float> embeddingVector;
for (const QJsonValue& value : embeddingArray) for (const auto &value: embeddingArray)
embeddingVector.push_back(static_cast<float>(value.toDouble())); embeddingVector.push_back(static_cast<float>(value.toDouble()));
EmbeddingResult result; EmbeddingResult result;
result.model = chunk.model;
result.folder_id = chunk.folder_id; result.folder_id = chunk.folder_id;
result.chunk_id = chunk.chunk_id; result.chunk_id = chunk.chunk_id;
result.embedding = std::move(embeddingVector); result.embedding = std::move(embeddingVector);
@ -267,10 +282,6 @@ void EmbeddingLLMWorker::handleFinished()
if (retrievedData.isValid() && retrievedData.canConvert<QVector<EmbeddingChunk>>()) if (retrievedData.isValid() && retrievedData.canConvert<QVector<EmbeddingChunk>>())
chunks = retrievedData.value<QVector<EmbeddingChunk>>(); chunks = retrievedData.value<QVector<EmbeddingChunk>>();
int folder_id = 0;
if (!chunks.isEmpty())
folder_id = chunks.first().folder_id;
QVariant response = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); QVariant response = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
Q_ASSERT(response.isValid()); Q_ASSERT(response.isValid());
bool ok; bool ok;
@ -279,13 +290,13 @@ void EmbeddingLLMWorker::handleFinished()
QString errorDetails; QString errorDetails;
QString replyErrorString = reply->errorString().trimmed(); QString replyErrorString = reply->errorString().trimmed();
QByteArray replyContent = reply->readAll().trimmed(); QByteArray replyContent = reply->readAll().trimmed();
errorDetails = QString("ERROR: Nomic Atlas responded with error code \"%1\"").arg(code); errorDetails = u"ERROR: Nomic Atlas responded with error code \"%1\""_s.arg(code);
if (!replyErrorString.isEmpty()) if (!replyErrorString.isEmpty())
errorDetails += QString(". Error Details: \"%1\"").arg(replyErrorString); errorDetails += u". Error Details: \"%1\""_s.arg(replyErrorString);
if (!replyContent.isEmpty()) if (!replyContent.isEmpty())
errorDetails += QString(". Response Content: \"%1\"").arg(QString::fromUtf8(replyContent)); errorDetails += u". Response Content: \"%1\""_s.arg(QString::fromUtf8(replyContent));
qWarning() << errorDetails; qWarning() << errorDetails;
emit errorGenerated(folder_id, errorDetails); emit errorGenerated(chunks, errorDetails);
return; return;
} }
@ -294,7 +305,7 @@ void EmbeddingLLMWorker::handleFinished()
QJsonParseError err; QJsonParseError err;
QJsonDocument document = QJsonDocument::fromJson(jsonData, &err); QJsonDocument document = QJsonDocument::fromJson(jsonData, &err);
if (err.error != QJsonParseError::NoError) { if (err.error != QJsonParseError::NoError) {
qWarning() << "ERROR: Couldn't parse Nomic Atlas response: " << jsonData << err.errorString(); qWarning() << "ERROR: Couldn't parse Nomic Atlas response:" << jsonData << err.errorString();
return; return;
} }
@ -315,8 +326,8 @@ EmbeddingLLM::EmbeddingLLM()
: QObject(nullptr) : QObject(nullptr)
, m_embeddingWorker(new EmbeddingLLMWorker) , m_embeddingWorker(new EmbeddingLLMWorker)
{ {
connect(this, &EmbeddingLLM::requestAsyncEmbedding, m_embeddingWorker, connect(this, &EmbeddingLLM::requestDocEmbeddings, m_embeddingWorker,
&EmbeddingLLMWorker::requestAsyncEmbedding, Qt::QueuedConnection); &EmbeddingLLMWorker::docEmbeddingsRequested, Qt::QueuedConnection);
connect(m_embeddingWorker, &EmbeddingLLMWorker::embeddingsGenerated, this, connect(m_embeddingWorker, &EmbeddingLLMWorker::embeddingsGenerated, this,
&EmbeddingLLM::embeddingsGenerated, Qt::QueuedConnection); &EmbeddingLLM::embeddingsGenerated, Qt::QueuedConnection);
connect(m_embeddingWorker, &EmbeddingLLMWorker::errorGenerated, this, connect(m_embeddingWorker, &EmbeddingLLMWorker::errorGenerated, this,
@ -329,26 +340,18 @@ EmbeddingLLM::~EmbeddingLLM()
m_embeddingWorker = nullptr; m_embeddingWorker = nullptr;
} }
std::vector<float> EmbeddingLLM::generateEmbeddings(const QString &text) QString EmbeddingLLM::model()
{ {
if (!m_embeddingWorker->hasModel() && !m_embeddingWorker->loadModel()) { return EMBEDDING_MODEL_NAME;
qWarning() << "WARNING: Could not load model for embeddings";
return {};
}
if (!m_embeddingWorker->isNomic()) {
return m_embeddingWorker->generateSyncEmbedding(text);
}
EmbeddingLLMWorker worker;
connect(this, &EmbeddingLLM::requestSyncEmbedding, &worker,
&EmbeddingLLMWorker::requestSyncEmbedding, Qt::QueuedConnection);
emit requestSyncEmbedding(text);
worker.wait();
return worker.lastResponse();
} }
void EmbeddingLLM::generateAsyncEmbeddings(const QVector<EmbeddingChunk> &chunks) // TODO(jared): embed using all necessary embedding models given collection
std::vector<float> EmbeddingLLM::generateQueryEmbedding(const QString &text)
{ {
emit requestAsyncEmbedding(chunks); return m_embeddingWorker->generateQueryEmbedding(text);
}
void EmbeddingLLM::generateDocEmbeddingsAsync(const QVector<EmbeddingChunk> &chunks)
{
emit requestDocEmbeddings(chunks);
} }

View File

@ -2,6 +2,7 @@
#define EMBLLM_H #define EMBLLM_H
#include <QByteArray> #include <QByteArray>
#include <QMutex>
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
@ -16,6 +17,7 @@ class LLModel;
class QNetworkAccessManager; class QNetworkAccessManager;
struct EmbeddingChunk { struct EmbeddingChunk {
QString model; // TODO(jared): use to select model
int folder_id; int folder_id;
int chunk_id; int chunk_id;
QString chunk; QString chunk;
@ -24,6 +26,7 @@ struct EmbeddingChunk {
Q_DECLARE_METATYPE(EmbeddingChunk) Q_DECLARE_METATYPE(EmbeddingChunk)
struct EmbeddingResult { struct EmbeddingResult {
QString model;
int folder_id; int folder_id;
int chunk_id; int chunk_id;
std::vector<float> embedding; std::vector<float> embedding;
@ -33,32 +36,33 @@ class EmbeddingLLMWorker : public QObject {
Q_OBJECT Q_OBJECT
public: public:
EmbeddingLLMWorker(); EmbeddingLLMWorker();
virtual ~EmbeddingLLMWorker(); ~EmbeddingLLMWorker() override;
void wait(); void wait();
std::vector<float> lastResponse() const { return m_lastResponse; } std::vector<float> lastResponse() const { return m_lastResponse; }
bool loadModel(); bool loadModel();
bool hasModel() const; bool isNomic() const { return !m_nomicAPIKey.isEmpty(); }
bool isNomic() const; bool hasModel() const { return isNomic() || m_model; }
std::vector<float> generateSyncEmbedding(const QString &text); std::vector<float> generateQueryEmbedding(const QString &text);
public Q_SLOTS: public Q_SLOTS:
void requestSyncEmbedding(const QString &text); void atlasQueryEmbeddingRequested(const QString &text);
void requestAsyncEmbedding(const QVector<EmbeddingChunk> &chunks); void docEmbeddingsRequested(const QVector<EmbeddingChunk> &chunks);
Q_SIGNALS: Q_SIGNALS:
void requestAtlasQueryEmbedding(const QString &text);
void embeddingsGenerated(const QVector<EmbeddingResult> &embeddings); void embeddingsGenerated(const QVector<EmbeddingResult> &embeddings);
void errorGenerated(int folder_id, const QString &error); void errorGenerated(const QVector<EmbeddingChunk> &chunks, const QString &error);
void finished(); void finished();
private Q_SLOTS: private Q_SLOTS:
void handleFinished(); void handleFinished();
private: private:
void sendAtlasRequest(const QStringList &texts, const QString &taskType, QVariant userData = {}); void sendAtlasRequest(const QStringList &texts, const QString &taskType, const QVariant &userData = {});
QString m_nomicAPIKey; QString m_nomicAPIKey;
QNetworkAccessManager *m_networkManager; QNetworkAccessManager *m_networkManager;
@ -66,6 +70,7 @@ private:
LLModel *m_model = nullptr; LLModel *m_model = nullptr;
std::atomic<bool> m_stopGenerating; std::atomic<bool> m_stopGenerating;
QThread m_workerThread; QThread m_workerThread;
QMutex m_mutex; // guards m_model and m_nomicAPIKey
}; };
class EmbeddingLLM : public QObject class EmbeddingLLM : public QObject
@ -73,20 +78,20 @@ class EmbeddingLLM : public QObject
Q_OBJECT Q_OBJECT
public: public:
EmbeddingLLM(); EmbeddingLLM();
virtual ~EmbeddingLLM(); ~EmbeddingLLM() override;
static QString model();
bool loadModel(); bool loadModel();
bool hasModel() const; bool hasModel() const;
public Q_SLOTS: public Q_SLOTS:
std::vector<float> generateEmbeddings(const QString &text); // synchronous std::vector<float> generateQueryEmbedding(const QString &text); // synchronous
void generateAsyncEmbeddings(const QVector<EmbeddingChunk> &chunks); void generateDocEmbeddingsAsync(const QVector<EmbeddingChunk> &chunks);
Q_SIGNALS: Q_SIGNALS:
void requestSyncEmbedding(const QString &text); void requestDocEmbeddings(const QVector<EmbeddingChunk> &chunks);
void requestAsyncEmbedding(const QVector<EmbeddingChunk> &chunks);
void embeddingsGenerated(const QVector<EmbeddingResult> &embeddings); void embeddingsGenerated(const QVector<EmbeddingResult> &embeddings);
void errorGenerated(int folder_id, const QString &error); void errorGenerated(const QVector<EmbeddingChunk> &chunks, const QString &error);
private: private:
EmbeddingLLMWorker *m_embeddingWorker; EmbeddingLLMWorker *m_embeddingWorker;

View File

@ -1,167 +0,0 @@
#pragma once
#include <unordered_map>
#include <fstream>
#include <mutex>
#include <algorithm>
#include <assert.h>
namespace hnswlib {
template<typename dist_t>
class BruteforceSearch : public AlgorithmInterface<dist_t> {
public:
char *data_;
size_t maxelements_;
size_t cur_element_count;
size_t size_per_element_;
size_t data_size_;
DISTFUNC <dist_t> fstdistfunc_;
void *dist_func_param_;
std::mutex index_lock;
std::unordered_map<labeltype, size_t > dict_external_to_internal;
BruteforceSearch(SpaceInterface <dist_t> *s)
: data_(nullptr),
maxelements_(0),
cur_element_count(0),
size_per_element_(0),
data_size_(0),
dist_func_param_(nullptr) {
}
BruteforceSearch(SpaceInterface<dist_t> *s, const std::string &location)
: data_(nullptr),
maxelements_(0),
cur_element_count(0),
size_per_element_(0),
data_size_(0),
dist_func_param_(nullptr) {
loadIndex(location, s);
}
BruteforceSearch(SpaceInterface <dist_t> *s, size_t maxElements) {
maxelements_ = maxElements;
data_size_ = s->get_data_size();
fstdistfunc_ = s->get_dist_func();
dist_func_param_ = s->get_dist_func_param();
size_per_element_ = data_size_ + sizeof(labeltype);
data_ = (char *) malloc(maxElements * size_per_element_);
if (data_ == nullptr)
throw std::runtime_error("Not enough memory: BruteforceSearch failed to allocate data");
cur_element_count = 0;
}
~BruteforceSearch() {
free(data_);
}
void addPoint(const void *datapoint, labeltype label, bool replace_deleted = false) {
int idx;
{
std::unique_lock<std::mutex> lock(index_lock);
auto search = dict_external_to_internal.find(label);
if (search != dict_external_to_internal.end()) {
idx = search->second;
} else {
if (cur_element_count >= maxelements_) {
throw std::runtime_error("The number of elements exceeds the specified limit\n");
}
idx = cur_element_count;
dict_external_to_internal[label] = idx;
cur_element_count++;
}
}
memcpy(data_ + size_per_element_ * idx + data_size_, &label, sizeof(labeltype));
memcpy(data_ + size_per_element_ * idx, datapoint, data_size_);
}
void removePoint(labeltype cur_external) {
size_t cur_c = dict_external_to_internal[cur_external];
dict_external_to_internal.erase(cur_external);
labeltype label = *((labeltype*)(data_ + size_per_element_ * (cur_element_count-1) + data_size_));
dict_external_to_internal[label] = cur_c;
memcpy(data_ + size_per_element_ * cur_c,
data_ + size_per_element_ * (cur_element_count-1),
data_size_+sizeof(labeltype));
cur_element_count--;
}
std::priority_queue<std::pair<dist_t, labeltype >>
searchKnn(const void *query_data, size_t k, BaseFilterFunctor* isIdAllowed = nullptr) const {
assert(k <= cur_element_count);
std::priority_queue<std::pair<dist_t, labeltype >> topResults;
if (cur_element_count == 0) return topResults;
for (int i = 0; i < k; i++) {
dist_t dist = fstdistfunc_(query_data, data_ + size_per_element_ * i, dist_func_param_);
labeltype label = *((labeltype*) (data_ + size_per_element_ * i + data_size_));
if ((!isIdAllowed) || (*isIdAllowed)(label)) {
topResults.push(std::pair<dist_t, labeltype>(dist, label));
}
}
dist_t lastdist = topResults.empty() ? std::numeric_limits<dist_t>::max() : topResults.top().first;
for (int i = k; i < cur_element_count; i++) {
dist_t dist = fstdistfunc_(query_data, data_ + size_per_element_ * i, dist_func_param_);
if (dist <= lastdist) {
labeltype label = *((labeltype *) (data_ + size_per_element_ * i + data_size_));
if ((!isIdAllowed) || (*isIdAllowed)(label)) {
topResults.push(std::pair<dist_t, labeltype>(dist, label));
}
if (topResults.size() > k)
topResults.pop();
if (!topResults.empty()) {
lastdist = topResults.top().first;
}
}
}
return topResults;
}
void saveIndex(const std::string &location) {
std::ofstream output(location, std::ios::binary);
std::streampos position;
writeBinaryPOD(output, maxelements_);
writeBinaryPOD(output, size_per_element_);
writeBinaryPOD(output, cur_element_count);
output.write(data_, maxelements_ * size_per_element_);
output.close();
}
void loadIndex(const std::string &location, SpaceInterface<dist_t> *s) {
std::ifstream input(location, std::ios::binary);
std::streampos position;
readBinaryPOD(input, maxelements_);
readBinaryPOD(input, size_per_element_);
readBinaryPOD(input, cur_element_count);
data_size_ = s->get_data_size();
fstdistfunc_ = s->get_dist_func();
dist_func_param_ = s->get_dist_func_param();
size_per_element_ = data_size_ + sizeof(labeltype);
data_ = (char *) malloc(maxelements_ * size_per_element_);
if (data_ == nullptr)
throw std::runtime_error("Not enough memory: loadIndex failed to allocate data");
input.read(data_, maxelements_ * size_per_element_);
input.close();
}
};
} // namespace hnswlib

File diff suppressed because it is too large Load Diff

View File

@ -1,199 +0,0 @@
#pragma once
#ifndef NO_MANUAL_VECTORIZATION
#if (defined(__SSE__) || _M_IX86_FP > 0 || defined(_M_AMD64) || defined(_M_X64))
#define USE_SSE
#ifdef __AVX__
#define USE_AVX
#ifdef __AVX512F__
#define USE_AVX512
#endif
#endif
#endif
#endif
#if defined(USE_AVX) || defined(USE_SSE)
#ifdef _MSC_VER
#include <intrin.h>
#include <stdexcept>
void cpuid(int32_t out[4], int32_t eax, int32_t ecx) {
__cpuidex(out, eax, ecx);
}
static __int64 xgetbv(unsigned int x) {
return _xgetbv(x);
}
#else
#include <x86intrin.h>
#include <cpuid.h>
#include <stdint.h>
static void cpuid(int32_t cpuInfo[4], int32_t eax, int32_t ecx) {
__cpuid_count(eax, ecx, cpuInfo[0], cpuInfo[1], cpuInfo[2], cpuInfo[3]);
}
static uint64_t xgetbv(unsigned int index) {
uint32_t eax, edx;
__asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index));
return ((uint64_t)edx << 32) | eax;
}
#endif
#if defined(USE_AVX512)
#include <immintrin.h>
#endif
#if defined(__GNUC__)
#define PORTABLE_ALIGN32 __attribute__((aligned(32)))
#define PORTABLE_ALIGN64 __attribute__((aligned(64)))
#else
#define PORTABLE_ALIGN32 __declspec(align(32))
#define PORTABLE_ALIGN64 __declspec(align(64))
#endif
// Adapted from https://github.com/Mysticial/FeatureDetector
#define _XCR_XFEATURE_ENABLED_MASK 0
static bool AVXCapable() {
int cpuInfo[4];
// CPU support
cpuid(cpuInfo, 0, 0);
int nIds = cpuInfo[0];
bool HW_AVX = false;
if (nIds >= 0x00000001) {
cpuid(cpuInfo, 0x00000001, 0);
HW_AVX = (cpuInfo[2] & ((int)1 << 28)) != 0;
}
// OS support
cpuid(cpuInfo, 1, 0);
bool osUsesXSAVE_XRSTORE = (cpuInfo[2] & (1 << 27)) != 0;
bool cpuAVXSuport = (cpuInfo[2] & (1 << 28)) != 0;
bool avxSupported = false;
if (osUsesXSAVE_XRSTORE && cpuAVXSuport) {
uint64_t xcrFeatureMask = xgetbv(_XCR_XFEATURE_ENABLED_MASK);
avxSupported = (xcrFeatureMask & 0x6) == 0x6;
}
return HW_AVX && avxSupported;
}
static bool AVX512Capable() {
if (!AVXCapable()) return false;
int cpuInfo[4];
// CPU support
cpuid(cpuInfo, 0, 0);
int nIds = cpuInfo[0];
bool HW_AVX512F = false;
if (nIds >= 0x00000007) { // AVX512 Foundation
cpuid(cpuInfo, 0x00000007, 0);
HW_AVX512F = (cpuInfo[1] & ((int)1 << 16)) != 0;
}
// OS support
cpuid(cpuInfo, 1, 0);
bool osUsesXSAVE_XRSTORE = (cpuInfo[2] & (1 << 27)) != 0;
bool cpuAVXSuport = (cpuInfo[2] & (1 << 28)) != 0;
bool avx512Supported = false;
if (osUsesXSAVE_XRSTORE && cpuAVXSuport) {
uint64_t xcrFeatureMask = xgetbv(_XCR_XFEATURE_ENABLED_MASK);
avx512Supported = (xcrFeatureMask & 0xe6) == 0xe6;
}
return HW_AVX512F && avx512Supported;
}
#endif
#include <queue>
#include <vector>
#include <iostream>
#include <string.h>
namespace hnswlib {
typedef size_t labeltype;
// This can be extended to store state for filtering (e.g. from a std::set)
class BaseFilterFunctor {
public:
virtual bool operator()(hnswlib::labeltype id) { return true; }
};
template <typename T>
class pairGreater {
public:
bool operator()(const T& p1, const T& p2) {
return p1.first > p2.first;
}
};
template<typename T>
static void writeBinaryPOD(std::ostream &out, const T &podRef) {
out.write((char *) &podRef, sizeof(T));
}
template<typename T>
static void readBinaryPOD(std::istream &in, T &podRef) {
in.read((char *) &podRef, sizeof(T));
}
template<typename MTYPE>
using DISTFUNC = MTYPE(*)(const void *, const void *, const void *);
template<typename MTYPE>
class SpaceInterface {
public:
// virtual void search(void *);
virtual size_t get_data_size() = 0;
virtual DISTFUNC<MTYPE> get_dist_func() = 0;
virtual void *get_dist_func_param() = 0;
virtual ~SpaceInterface() {}
};
template<typename dist_t>
class AlgorithmInterface {
public:
virtual void addPoint(const void *datapoint, labeltype label, bool replace_deleted = false) = 0;
virtual std::priority_queue<std::pair<dist_t, labeltype>>
searchKnn(const void*, size_t, BaseFilterFunctor* isIdAllowed = nullptr) const = 0;
// Return k nearest neighbor in the order of closer fist
virtual std::vector<std::pair<dist_t, labeltype>>
searchKnnCloserFirst(const void* query_data, size_t k, BaseFilterFunctor* isIdAllowed = nullptr) const;
virtual void saveIndex(const std::string &location) = 0;
virtual ~AlgorithmInterface(){
}
};
template<typename dist_t>
std::vector<std::pair<dist_t, labeltype>>
AlgorithmInterface<dist_t>::searchKnnCloserFirst(const void* query_data, size_t k,
BaseFilterFunctor* isIdAllowed) const {
std::vector<std::pair<dist_t, labeltype>> result;
// here searchKnn returns the result in the order of further first
auto ret = searchKnn(query_data, k, isIdAllowed);
{
size_t sz = ret.size();
result.resize(sz);
while (!ret.empty()) {
result[--sz] = ret.top();
ret.pop();
}
}
return result;
}
} // namespace hnswlib
#include "space_l2.h"
#include "space_ip.h"
#include "bruteforce.h"
#include "hnswalg.h"

View File

@ -1,375 +0,0 @@
#pragma once
#include "hnswlib.h"
namespace hnswlib {
static float
InnerProduct(const void *pVect1, const void *pVect2, const void *qty_ptr) {
size_t qty = *((size_t *) qty_ptr);
float res = 0;
for (unsigned i = 0; i < qty; i++) {
res += ((float *) pVect1)[i] * ((float *) pVect2)[i];
}
return res;
}
static float
InnerProductDistance(const void *pVect1, const void *pVect2, const void *qty_ptr) {
return 1.0f - InnerProduct(pVect1, pVect2, qty_ptr);
}
#if defined(USE_AVX)
// Favor using AVX if available.
static float
InnerProductSIMD4ExtAVX(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
float PORTABLE_ALIGN32 TmpRes[8];
float *pVect1 = (float *) pVect1v;
float *pVect2 = (float *) pVect2v;
size_t qty = *((size_t *) qty_ptr);
size_t qty16 = qty / 16;
size_t qty4 = qty / 4;
const float *pEnd1 = pVect1 + 16 * qty16;
const float *pEnd2 = pVect1 + 4 * qty4;
__m256 sum256 = _mm256_set1_ps(0);
while (pVect1 < pEnd1) {
//_mm_prefetch((char*)(pVect2 + 16), _MM_HINT_T0);
__m256 v1 = _mm256_loadu_ps(pVect1);
pVect1 += 8;
__m256 v2 = _mm256_loadu_ps(pVect2);
pVect2 += 8;
sum256 = _mm256_add_ps(sum256, _mm256_mul_ps(v1, v2));
v1 = _mm256_loadu_ps(pVect1);
pVect1 += 8;
v2 = _mm256_loadu_ps(pVect2);
pVect2 += 8;
sum256 = _mm256_add_ps(sum256, _mm256_mul_ps(v1, v2));
}
__m128 v1, v2;
__m128 sum_prod = _mm_add_ps(_mm256_extractf128_ps(sum256, 0), _mm256_extractf128_ps(sum256, 1));
while (pVect1 < pEnd2) {
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2));
}
_mm_store_ps(TmpRes, sum_prod);
float sum = TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3];
return sum;
}
static float
InnerProductDistanceSIMD4ExtAVX(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
return 1.0f - InnerProductSIMD4ExtAVX(pVect1v, pVect2v, qty_ptr);
}
#endif
#if defined(USE_SSE)
static float
InnerProductSIMD4ExtSSE(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
float PORTABLE_ALIGN32 TmpRes[8];
float *pVect1 = (float *) pVect1v;
float *pVect2 = (float *) pVect2v;
size_t qty = *((size_t *) qty_ptr);
size_t qty16 = qty / 16;
size_t qty4 = qty / 4;
const float *pEnd1 = pVect1 + 16 * qty16;
const float *pEnd2 = pVect1 + 4 * qty4;
__m128 v1, v2;
__m128 sum_prod = _mm_set1_ps(0);
while (pVect1 < pEnd1) {
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2));
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2));
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2));
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2));
}
while (pVect1 < pEnd2) {
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2));
}
_mm_store_ps(TmpRes, sum_prod);
float sum = TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3];
return sum;
}
static float
InnerProductDistanceSIMD4ExtSSE(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
return 1.0f - InnerProductSIMD4ExtSSE(pVect1v, pVect2v, qty_ptr);
}
#endif
#if defined(USE_AVX512)
static float
InnerProductSIMD16ExtAVX512(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
float PORTABLE_ALIGN64 TmpRes[16];
float *pVect1 = (float *) pVect1v;
float *pVect2 = (float *) pVect2v;
size_t qty = *((size_t *) qty_ptr);
size_t qty16 = qty / 16;
const float *pEnd1 = pVect1 + 16 * qty16;
__m512 sum512 = _mm512_set1_ps(0);
while (pVect1 < pEnd1) {
//_mm_prefetch((char*)(pVect2 + 16), _MM_HINT_T0);
__m512 v1 = _mm512_loadu_ps(pVect1);
pVect1 += 16;
__m512 v2 = _mm512_loadu_ps(pVect2);
pVect2 += 16;
sum512 = _mm512_add_ps(sum512, _mm512_mul_ps(v1, v2));
}
_mm512_store_ps(TmpRes, sum512);
float sum = TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3] + TmpRes[4] + TmpRes[5] + TmpRes[6] + TmpRes[7] + TmpRes[8] + TmpRes[9] + TmpRes[10] + TmpRes[11] + TmpRes[12] + TmpRes[13] + TmpRes[14] + TmpRes[15];
return sum;
}
static float
InnerProductDistanceSIMD16ExtAVX512(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
return 1.0f - InnerProductSIMD16ExtAVX512(pVect1v, pVect2v, qty_ptr);
}
#endif
#if defined(USE_AVX)
static float
InnerProductSIMD16ExtAVX(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
float PORTABLE_ALIGN32 TmpRes[8];
float *pVect1 = (float *) pVect1v;
float *pVect2 = (float *) pVect2v;
size_t qty = *((size_t *) qty_ptr);
size_t qty16 = qty / 16;
const float *pEnd1 = pVect1 + 16 * qty16;
__m256 sum256 = _mm256_set1_ps(0);
while (pVect1 < pEnd1) {
//_mm_prefetch((char*)(pVect2 + 16), _MM_HINT_T0);
__m256 v1 = _mm256_loadu_ps(pVect1);
pVect1 += 8;
__m256 v2 = _mm256_loadu_ps(pVect2);
pVect2 += 8;
sum256 = _mm256_add_ps(sum256, _mm256_mul_ps(v1, v2));
v1 = _mm256_loadu_ps(pVect1);
pVect1 += 8;
v2 = _mm256_loadu_ps(pVect2);
pVect2 += 8;
sum256 = _mm256_add_ps(sum256, _mm256_mul_ps(v1, v2));
}
_mm256_store_ps(TmpRes, sum256);
float sum = TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3] + TmpRes[4] + TmpRes[5] + TmpRes[6] + TmpRes[7];
return sum;
}
static float
InnerProductDistanceSIMD16ExtAVX(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
return 1.0f - InnerProductSIMD16ExtAVX(pVect1v, pVect2v, qty_ptr);
}
#endif
#if defined(USE_SSE)
static float
InnerProductSIMD16ExtSSE(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
float PORTABLE_ALIGN32 TmpRes[8];
float *pVect1 = (float *) pVect1v;
float *pVect2 = (float *) pVect2v;
size_t qty = *((size_t *) qty_ptr);
size_t qty16 = qty / 16;
const float *pEnd1 = pVect1 + 16 * qty16;
__m128 v1, v2;
__m128 sum_prod = _mm_set1_ps(0);
while (pVect1 < pEnd1) {
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2));
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2));
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2));
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2));
}
_mm_store_ps(TmpRes, sum_prod);
float sum = TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3];
return sum;
}
static float
InnerProductDistanceSIMD16ExtSSE(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
return 1.0f - InnerProductSIMD16ExtSSE(pVect1v, pVect2v, qty_ptr);
}
#endif
#if defined(USE_SSE) || defined(USE_AVX) || defined(USE_AVX512)
static DISTFUNC<float> InnerProductSIMD16Ext = InnerProductSIMD16ExtSSE;
static DISTFUNC<float> InnerProductSIMD4Ext = InnerProductSIMD4ExtSSE;
static DISTFUNC<float> InnerProductDistanceSIMD16Ext = InnerProductDistanceSIMD16ExtSSE;
static DISTFUNC<float> InnerProductDistanceSIMD4Ext = InnerProductDistanceSIMD4ExtSSE;
static float
InnerProductDistanceSIMD16ExtResiduals(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
size_t qty = *((size_t *) qty_ptr);
size_t qty16 = qty >> 4 << 4;
float res = InnerProductSIMD16Ext(pVect1v, pVect2v, &qty16);
float *pVect1 = (float *) pVect1v + qty16;
float *pVect2 = (float *) pVect2v + qty16;
size_t qty_left = qty - qty16;
float res_tail = InnerProduct(pVect1, pVect2, &qty_left);
return 1.0f - (res + res_tail);
}
static float
InnerProductDistanceSIMD4ExtResiduals(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
size_t qty = *((size_t *) qty_ptr);
size_t qty4 = qty >> 2 << 2;
float res = InnerProductSIMD4Ext(pVect1v, pVect2v, &qty4);
size_t qty_left = qty - qty4;
float *pVect1 = (float *) pVect1v + qty4;
float *pVect2 = (float *) pVect2v + qty4;
float res_tail = InnerProduct(pVect1, pVect2, &qty_left);
return 1.0f - (res + res_tail);
}
#endif
class InnerProductSpace : public SpaceInterface<float> {
DISTFUNC<float> fstdistfunc_;
size_t data_size_;
size_t dim_;
public:
InnerProductSpace(size_t dim) {
fstdistfunc_ = InnerProductDistance;
#if defined(USE_AVX) || defined(USE_SSE) || defined(USE_AVX512)
#if defined(USE_AVX512)
if (AVX512Capable()) {
InnerProductSIMD16Ext = InnerProductSIMD16ExtAVX512;
InnerProductDistanceSIMD16Ext = InnerProductDistanceSIMD16ExtAVX512;
} else if (AVXCapable()) {
InnerProductSIMD16Ext = InnerProductSIMD16ExtAVX;
InnerProductDistanceSIMD16Ext = InnerProductDistanceSIMD16ExtAVX;
}
#elif defined(USE_AVX)
if (AVXCapable()) {
InnerProductSIMD16Ext = InnerProductSIMD16ExtAVX;
InnerProductDistanceSIMD16Ext = InnerProductDistanceSIMD16ExtAVX;
}
#endif
#if defined(USE_AVX)
if (AVXCapable()) {
InnerProductSIMD4Ext = InnerProductSIMD4ExtAVX;
InnerProductDistanceSIMD4Ext = InnerProductDistanceSIMD4ExtAVX;
}
#endif
if (dim % 16 == 0)
fstdistfunc_ = InnerProductDistanceSIMD16Ext;
else if (dim % 4 == 0)
fstdistfunc_ = InnerProductDistanceSIMD4Ext;
else if (dim > 16)
fstdistfunc_ = InnerProductDistanceSIMD16ExtResiduals;
else if (dim > 4)
fstdistfunc_ = InnerProductDistanceSIMD4ExtResiduals;
#endif
dim_ = dim;
data_size_ = dim * sizeof(float);
}
size_t get_data_size() {
return data_size_;
}
DISTFUNC<float> get_dist_func() {
return fstdistfunc_;
}
void *get_dist_func_param() {
return &dim_;
}
~InnerProductSpace() {}
};
} // namespace hnswlib

View File

@ -1,324 +0,0 @@
#pragma once
#include "hnswlib.h"
namespace hnswlib {
static float
L2Sqr(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
float *pVect1 = (float *) pVect1v;
float *pVect2 = (float *) pVect2v;
size_t qty = *((size_t *) qty_ptr);
float res = 0;
for (size_t i = 0; i < qty; i++) {
float t = *pVect1 - *pVect2;
pVect1++;
pVect2++;
res += t * t;
}
return (res);
}
#if defined(USE_AVX512)
// Favor using AVX512 if available.
static float
L2SqrSIMD16ExtAVX512(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
float *pVect1 = (float *) pVect1v;
float *pVect2 = (float *) pVect2v;
size_t qty = *((size_t *) qty_ptr);
float PORTABLE_ALIGN64 TmpRes[16];
size_t qty16 = qty >> 4;
const float *pEnd1 = pVect1 + (qty16 << 4);
__m512 diff, v1, v2;
__m512 sum = _mm512_set1_ps(0);
while (pVect1 < pEnd1) {
v1 = _mm512_loadu_ps(pVect1);
pVect1 += 16;
v2 = _mm512_loadu_ps(pVect2);
pVect2 += 16;
diff = _mm512_sub_ps(v1, v2);
// sum = _mm512_fmadd_ps(diff, diff, sum);
sum = _mm512_add_ps(sum, _mm512_mul_ps(diff, diff));
}
_mm512_store_ps(TmpRes, sum);
float res = TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3] + TmpRes[4] + TmpRes[5] + TmpRes[6] +
TmpRes[7] + TmpRes[8] + TmpRes[9] + TmpRes[10] + TmpRes[11] + TmpRes[12] +
TmpRes[13] + TmpRes[14] + TmpRes[15];
return (res);
}
#endif
#if defined(USE_AVX)
// Favor using AVX if available.
static float
L2SqrSIMD16ExtAVX(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
float *pVect1 = (float *) pVect1v;
float *pVect2 = (float *) pVect2v;
size_t qty = *((size_t *) qty_ptr);
float PORTABLE_ALIGN32 TmpRes[8];
size_t qty16 = qty >> 4;
const float *pEnd1 = pVect1 + (qty16 << 4);
__m256 diff, v1, v2;
__m256 sum = _mm256_set1_ps(0);
while (pVect1 < pEnd1) {
v1 = _mm256_loadu_ps(pVect1);
pVect1 += 8;
v2 = _mm256_loadu_ps(pVect2);
pVect2 += 8;
diff = _mm256_sub_ps(v1, v2);
sum = _mm256_add_ps(sum, _mm256_mul_ps(diff, diff));
v1 = _mm256_loadu_ps(pVect1);
pVect1 += 8;
v2 = _mm256_loadu_ps(pVect2);
pVect2 += 8;
diff = _mm256_sub_ps(v1, v2);
sum = _mm256_add_ps(sum, _mm256_mul_ps(diff, diff));
}
_mm256_store_ps(TmpRes, sum);
return TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3] + TmpRes[4] + TmpRes[5] + TmpRes[6] + TmpRes[7];
}
#endif
#if defined(USE_SSE)
static float
L2SqrSIMD16ExtSSE(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
float *pVect1 = (float *) pVect1v;
float *pVect2 = (float *) pVect2v;
size_t qty = *((size_t *) qty_ptr);
float PORTABLE_ALIGN32 TmpRes[8];
size_t qty16 = qty >> 4;
const float *pEnd1 = pVect1 + (qty16 << 4);
__m128 diff, v1, v2;
__m128 sum = _mm_set1_ps(0);
while (pVect1 < pEnd1) {
//_mm_prefetch((char*)(pVect2 + 16), _MM_HINT_T0);
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
diff = _mm_sub_ps(v1, v2);
sum = _mm_add_ps(sum, _mm_mul_ps(diff, diff));
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
diff = _mm_sub_ps(v1, v2);
sum = _mm_add_ps(sum, _mm_mul_ps(diff, diff));
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
diff = _mm_sub_ps(v1, v2);
sum = _mm_add_ps(sum, _mm_mul_ps(diff, diff));
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
diff = _mm_sub_ps(v1, v2);
sum = _mm_add_ps(sum, _mm_mul_ps(diff, diff));
}
_mm_store_ps(TmpRes, sum);
return TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3];
}
#endif
#if defined(USE_SSE) || defined(USE_AVX) || defined(USE_AVX512)
static DISTFUNC<float> L2SqrSIMD16Ext = L2SqrSIMD16ExtSSE;
static float
L2SqrSIMD16ExtResiduals(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
size_t qty = *((size_t *) qty_ptr);
size_t qty16 = qty >> 4 << 4;
float res = L2SqrSIMD16Ext(pVect1v, pVect2v, &qty16);
float *pVect1 = (float *) pVect1v + qty16;
float *pVect2 = (float *) pVect2v + qty16;
size_t qty_left = qty - qty16;
float res_tail = L2Sqr(pVect1, pVect2, &qty_left);
return (res + res_tail);
}
#endif
#if defined(USE_SSE)
static float
L2SqrSIMD4Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
float PORTABLE_ALIGN32 TmpRes[8];
float *pVect1 = (float *) pVect1v;
float *pVect2 = (float *) pVect2v;
size_t qty = *((size_t *) qty_ptr);
size_t qty4 = qty >> 2;
const float *pEnd1 = pVect1 + (qty4 << 2);
__m128 diff, v1, v2;
__m128 sum = _mm_set1_ps(0);
while (pVect1 < pEnd1) {
v1 = _mm_loadu_ps(pVect1);
pVect1 += 4;
v2 = _mm_loadu_ps(pVect2);
pVect2 += 4;
diff = _mm_sub_ps(v1, v2);
sum = _mm_add_ps(sum, _mm_mul_ps(diff, diff));
}
_mm_store_ps(TmpRes, sum);
return TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3];
}
static float
L2SqrSIMD4ExtResiduals(const void *pVect1v, const void *pVect2v, const void *qty_ptr) {
size_t qty = *((size_t *) qty_ptr);
size_t qty4 = qty >> 2 << 2;
float res = L2SqrSIMD4Ext(pVect1v, pVect2v, &qty4);
size_t qty_left = qty - qty4;
float *pVect1 = (float *) pVect1v + qty4;
float *pVect2 = (float *) pVect2v + qty4;
float res_tail = L2Sqr(pVect1, pVect2, &qty_left);
return (res + res_tail);
}
#endif
class L2Space : public SpaceInterface<float> {
DISTFUNC<float> fstdistfunc_;
size_t data_size_;
size_t dim_;
public:
L2Space(size_t dim) {
fstdistfunc_ = L2Sqr;
#if defined(USE_SSE) || defined(USE_AVX) || defined(USE_AVX512)
#if defined(USE_AVX512)
if (AVX512Capable())
L2SqrSIMD16Ext = L2SqrSIMD16ExtAVX512;
else if (AVXCapable())
L2SqrSIMD16Ext = L2SqrSIMD16ExtAVX;
#elif defined(USE_AVX)
if (AVXCapable())
L2SqrSIMD16Ext = L2SqrSIMD16ExtAVX;
#endif
if (dim % 16 == 0)
fstdistfunc_ = L2SqrSIMD16Ext;
else if (dim % 4 == 0)
fstdistfunc_ = L2SqrSIMD4Ext;
else if (dim > 16)
fstdistfunc_ = L2SqrSIMD16ExtResiduals;
else if (dim > 4)
fstdistfunc_ = L2SqrSIMD4ExtResiduals;
#endif
dim_ = dim;
data_size_ = dim * sizeof(float);
}
size_t get_data_size() {
return data_size_;
}
DISTFUNC<float> get_dist_func() {
return fstdistfunc_;
}
void *get_dist_func_param() {
return &dim_;
}
~L2Space() {}
};
static int
L2SqrI4x(const void *__restrict pVect1, const void *__restrict pVect2, const void *__restrict qty_ptr) {
size_t qty = *((size_t *) qty_ptr);
int res = 0;
unsigned char *a = (unsigned char *) pVect1;
unsigned char *b = (unsigned char *) pVect2;
qty = qty >> 2;
for (size_t i = 0; i < qty; i++) {
res += ((*a) - (*b)) * ((*a) - (*b));
a++;
b++;
res += ((*a) - (*b)) * ((*a) - (*b));
a++;
b++;
res += ((*a) - (*b)) * ((*a) - (*b));
a++;
b++;
res += ((*a) - (*b)) * ((*a) - (*b));
a++;
b++;
}
return (res);
}
static int L2SqrI(const void* __restrict pVect1, const void* __restrict pVect2, const void* __restrict qty_ptr) {
size_t qty = *((size_t*)qty_ptr);
int res = 0;
unsigned char* a = (unsigned char*)pVect1;
unsigned char* b = (unsigned char*)pVect2;
for (size_t i = 0; i < qty; i++) {
res += ((*a) - (*b)) * ((*a) - (*b));
a++;
b++;
}
return (res);
}
class L2SpaceI : public SpaceInterface<int> {
DISTFUNC<int> fstdistfunc_;
size_t data_size_;
size_t dim_;
public:
L2SpaceI(size_t dim) {
if (dim % 4 == 0) {
fstdistfunc_ = L2SqrI4x;
} else {
fstdistfunc_ = L2SqrI;
}
dim_ = dim;
data_size_ = dim * sizeof(unsigned char);
}
size_t get_data_size() {
return data_size_;
}
DISTFUNC<int> get_dist_func() {
return fstdistfunc_;
}
void *get_dist_func_param() {
return &dim_;
}
~L2SpaceI() {}
};
} // namespace hnswlib

View File

@ -1,78 +0,0 @@
#pragma once
#include <mutex>
#include <string.h>
#include <deque>
namespace hnswlib {
typedef unsigned short int vl_type;
class VisitedList {
public:
vl_type curV;
vl_type *mass;
unsigned int numelements;
VisitedList(int numelements1) {
curV = -1;
numelements = numelements1;
mass = new vl_type[numelements];
}
void reset() {
curV++;
if (curV == 0) {
memset(mass, 0, sizeof(vl_type) * numelements);
curV++;
}
}
~VisitedList() { delete[] mass; }
};
///////////////////////////////////////////////////////////
//
// Class for multi-threaded pool-management of VisitedLists
//
/////////////////////////////////////////////////////////
class VisitedListPool {
std::deque<VisitedList *> pool;
std::mutex poolguard;
int numelements;
public:
VisitedListPool(int initmaxpools, int numelements1) {
numelements = numelements1;
for (int i = 0; i < initmaxpools; i++)
pool.push_front(new VisitedList(numelements));
}
VisitedList *getFreeVisitedList() {
VisitedList *rez;
{
std::unique_lock <std::mutex> lock(poolguard);
if (pool.size() > 0) {
rez = pool.front();
pool.pop_front();
} else {
rez = new VisitedList(numelements);
}
}
rez->reset();
return rez;
}
void releaseVisitedList(VisitedList *vl) {
std::unique_lock <std::mutex> lock(poolguard);
pool.push_front(vl);
}
~VisitedListPool() {
while (pool.size()) {
VisitedList *rez = pool.front();
pool.pop_front();
delete rez;
}
}
};
} // namespace hnswlib

View File

@ -0,0 +1,52 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M37.6479 31.9833C37.6479 35.1121 35.1116 37.6485 31.9828 37.6485C28.854 37.6485 26.3176 35.1121 26.3176 31.9833C26.3176 28.8546 28.854 26.3182 31.9828 26.3182C35.1116 26.3182 37.6479 28.8546 37.6479 31.9833Z" fill="#5E895F"/>
<path d="M9.11966 35.898C9.93846 35.7091 10.757 36.2269 10.9479 37.0544C11.1387 37.8819 10.6297 38.7058 9.81093 38.8947C8.99214 39.0836 8.17362 38.5659 7.98273 37.7384C7.79185 36.9109 8.30087 36.0869 9.11966 35.898Z" fill="#5E895F"/>
<path d="M9.78115 25.5231C10.6008 25.7082 11.1136 26.5298 10.9266 27.3582C10.7395 28.1865 9.92342 28.708 9.10376 28.5229C8.2841 28.3378 7.77127 27.5163 7.95833 26.6879C8.14538 25.8595 8.96149 25.338 9.78115 25.5231Z" fill="#5E895F"/>
<path d="M14.8761 16.462C15.5343 16.9844 15.6399 17.9471 15.1119 18.6123C14.5839 19.2775 13.6224 19.3932 12.9642 18.8708C12.306 18.3484 12.2005 17.3857 12.7284 16.7205C13.2564 16.0553 14.2179 15.9396 14.8761 16.462Z" fill="#5E895F"/>
<path d="M23.4003 10.5106C23.7666 11.2668 23.444 12.18 22.6797 12.5503C21.9155 12.9205 20.9989 12.6076 20.6326 11.8513C20.2662 11.0951 20.5888 10.1819 21.3531 9.81168C22.1174 9.44144 23.0339 9.75435 23.4003 10.5106Z" fill="#5E895F"/>
<path d="M33.6609 8.84555C33.6629 9.68585 32.976 10.3686 32.1268 10.3706C31.2775 10.3726 30.5875 9.69297 30.5856 8.85267C30.5836 8.01237 31.2705 7.32958 32.1197 7.32762C32.9689 7.32565 33.659 8.00526 33.6609 8.84555Z" fill="#5E895F"/>
<path d="M43.6291 11.7981C43.2663 12.556 42.3512 12.8731 41.5852 12.5064C40.8192 12.1397 40.4924 11.228 40.8552 10.4701C41.2181 9.7122 42.1332 9.39505 42.8992 9.76175C43.6651 10.1284 43.992 11.0401 43.6291 11.7981Z" fill="#5E895F"/>
<path d="M51.3299 18.7825C50.6741 19.308 49.7121 19.1967 49.181 18.5339C48.65 17.8712 48.7511 16.908 49.4069 16.3825C50.0626 15.8571 51.0247 15.9684 51.5557 16.6311C52.0868 17.2939 51.9857 18.2571 51.3299 18.7825Z" fill="#5E895F"/>
<path d="M55.236 28.4162C54.4173 28.6051 53.5987 28.0873 53.4079 27.2598C53.217 26.4323 53.726 25.6084 54.5448 25.4195C55.3636 25.2306 56.1821 25.7483 56.373 26.5758C56.5639 27.4034 56.0548 28.2273 55.236 28.4162Z" fill="#5E895F"/>
<path d="M54.5753 38.7908C53.7556 38.6057 53.2428 37.7841 53.4299 36.9558C53.6169 36.1274 54.433 35.6059 55.2527 35.791C56.0723 35.9761 56.5852 36.7976 56.3981 37.626C56.2111 38.4544 55.395 38.9759 54.5753 38.7908Z" fill="#5E895F"/>
<path d="M49.4784 47.8522C48.8202 47.3299 48.7146 46.3671 49.2426 45.7019C49.7705 45.0368 50.7321 44.921 51.3903 45.4434C52.0485 45.9658 52.154 46.9285 51.6261 47.5937C51.0981 48.2589 50.1366 48.3746 49.4784 47.8522Z" fill="#5E895F"/>
<path d="M40.9562 53.8039C40.5898 53.0477 40.9124 52.1345 41.6767 51.7643C42.441 51.394 43.3575 51.7069 43.7239 52.4632C44.0902 53.2194 43.7676 54.1326 43.0033 54.5028C42.2391 54.8731 41.3225 54.5602 40.9562 53.8039Z" fill="#5E895F"/>
<path d="M30.6936 55.4687C30.6916 54.6284 31.3785 53.9456 32.2277 53.9436C33.077 53.9416 33.767 54.6212 33.7689 55.4615C33.7709 56.3018 33.084 56.9846 32.2348 56.9866C31.3855 56.9886 30.6955 56.309 30.6936 55.4687Z" fill="#5E895F"/>
<path d="M20.7273 52.5168C21.0902 51.7588 22.0053 51.4417 22.7713 51.8084C23.5372 52.1751 23.8641 53.0868 23.5012 53.8447C23.1384 54.6026 22.2233 54.9198 21.4573 54.5531C20.6913 54.1864 20.3645 53.2747 20.7273 52.5168Z" fill="#5E895F"/>
<path d="M13.0273 45.5313C13.683 45.0058 14.6451 45.1171 15.1761 45.7799C15.7072 46.4426 15.6061 47.4058 14.9503 47.9312C14.2945 48.4567 13.3325 48.3454 12.8014 47.6826C12.2704 47.0199 12.3715 46.0567 13.0273 45.5313Z" fill="#5E895F"/>
<path d="M0.760749 31.223C1.1809 31.223 1.5215 31.5708 1.5215 31.9999C1.5215 32.429 1.1809 32.7769 0.760749 32.7769C0.340599 32.7769 0 32.429 0 31.9999C0 31.5708 0.340599 31.223 0.760749 31.223Z" fill="#5E895F"/>
<path d="M1.71611 24.291C2.12572 24.3845 2.38038 24.7994 2.2849 25.2177C2.18942 25.636 1.77995 25.8994 1.37034 25.8059C0.960721 25.7124 0.706065 25.2975 0.801546 24.8791C0.897028 24.4608 1.30649 24.1975 1.71611 24.291Z" fill="#5E895F"/>
<path d="M4.19164 17.7456C4.57018 17.9279 4.72613 18.3891 4.53995 18.7757C4.35378 19.1623 3.89598 19.3279 3.51744 19.1456C3.1389 18.9633 2.98296 18.5022 3.16913 18.1156C3.35531 17.729 3.8131 17.5633 4.19164 17.7456Z" fill="#5E895F"/>
<path d="M8.05994 11.9149C8.38842 12.1769 8.43784 12.6612 8.1703 12.9967C7.90277 13.3321 7.4196 13.3917 7.09112 13.1298C6.76263 12.8678 6.71322 12.3835 6.98075 12.048C7.24828 11.7125 7.73145 11.653 8.05994 11.9149Z" fill="#5E895F"/>
<path d="M13.1303 7.09165C13.3923 7.42014 13.3327 7.90331 12.9972 8.17084C12.6618 8.43837 12.1774 8.38896 11.9155 8.06048C11.6535 7.73199 11.7131 7.24882 12.0486 6.98129C12.3841 6.71375 12.8684 6.76317 13.1303 7.09165Z" fill="#5E895F"/>
<path d="M19.1463 3.51689C19.3286 3.89543 19.163 4.35322 18.7764 4.5394C18.3898 4.72557 17.9286 4.56963 17.7463 4.19109C17.564 3.81255 17.7297 3.35475 18.1163 3.16858C18.5028 2.9824 18.964 3.13835 19.1463 3.51689Z" fill="#5E895F"/>
<path d="M25.8055 1.37137C25.899 1.78099 25.6357 2.19045 25.2174 2.28593C24.799 2.38141 24.3841 2.12676 24.2906 1.71714C24.1971 1.30752 24.4605 0.898061 24.8788 0.80258C25.2971 0.707098 25.712 0.961755 25.8055 1.37137Z" fill="#5E895F"/>
<path d="M32.7763 0.760749C32.7763 1.1809 32.4284 1.5215 31.9993 1.5215C31.5703 1.5215 31.2224 1.1809 31.2224 0.760749C31.2224 0.340599 31.5703 0 31.9993 0C32.4284 0 32.7763 0.340599 32.7763 0.760749Z" fill="#5E895F"/>
<path d="M39.7084 1.71684C39.6149 2.12645 39.2 2.38111 38.7817 2.28563C38.3634 2.19015 38.1 1.78069 38.1935 1.37107C38.287 0.961454 38.7019 0.706797 39.1202 0.802279C39.5386 0.89776 39.8019 1.30722 39.7084 1.71684Z" fill="#5E895F"/>
<path d="M46.2537 4.19176C46.0715 4.57031 45.6103 4.72625 45.2237 4.54007C44.8371 4.3539 44.6715 3.89611 44.8538 3.51756C45.0361 3.13902 45.4972 2.98308 45.8838 3.16925C46.2704 3.35543 46.436 3.81322 46.2537 4.19176Z" fill="#5E895F"/>
<path d="M52.0836 8.06055C51.8216 8.38903 51.3373 8.43845 51.0018 8.17091C50.6663 7.90338 50.6067 7.42021 50.8687 7.09173C51.1307 6.76324 51.615 6.71383 51.9504 6.98136C52.2859 7.24889 52.3455 7.73206 52.0836 8.06055Z" fill="#5E895F"/>
<path d="M56.909 13.1303C56.5805 13.3922 56.0973 13.3326 55.8298 12.9972C55.5622 12.6617 55.6116 12.1774 55.9401 11.9154C56.2686 11.6535 56.7518 11.7131 57.0193 12.0485C57.2869 12.384 57.2374 12.8683 56.909 13.1303Z" fill="#5E895F"/>
<path d="M60.4837 19.1466C60.1051 19.3289 59.6473 19.1633 59.4612 18.7767C59.275 18.3901 59.4309 17.9289 59.8095 17.7466C60.188 17.5643 60.6458 17.73 60.832 18.1166C61.0181 18.5032 60.8622 18.9643 60.4837 19.1466Z" fill="#5E895F"/>
<path d="M62.6309 25.8061C62.2213 25.8996 61.8119 25.6363 61.7164 25.218C61.6209 24.7996 61.8756 24.3847 62.2852 24.2912C62.6948 24.1977 63.1043 24.4611 63.1997 24.8794C63.2952 25.2977 63.0406 25.7126 62.6309 25.8061Z" fill="#5E895F"/>
<path d="M63.2393 32.777C62.8191 32.777 62.4785 32.4292 62.4785 32.0001C62.4785 31.571 62.8191 31.2231 63.2393 31.2231C63.6594 31.2231 64 31.571 64 32.0001C64 32.4292 63.6594 32.777 63.2393 32.777Z" fill="#5E895F"/>
<path d="M62.2839 39.709C61.8743 39.6155 61.6196 39.2006 61.7151 38.7823C61.8106 38.364 62.22 38.1006 62.6297 38.1941C63.0393 38.2876 63.2939 38.7025 63.1985 39.1209C63.103 39.5392 62.6935 39.8025 62.2839 39.709Z" fill="#5E895F"/>
<path d="M59.8084 46.2544C59.4298 46.0721 59.2739 45.6109 59.46 45.2243C59.6462 44.8377 60.104 44.6721 60.4826 44.8544C60.8611 45.0367 61.017 45.4978 60.8309 45.8844C60.6447 46.271 60.1869 46.4367 59.8084 46.2544Z" fill="#5E895F"/>
<path d="M55.9401 52.0851C55.6116 51.8231 55.5622 51.3388 55.8297 51.0033C56.0972 50.6679 56.5804 50.6083 56.9089 50.8702C57.2374 51.1322 57.2868 51.6165 57.0193 51.952C56.7517 52.2875 56.2685 52.347 55.9401 52.0851Z" fill="#5E895F"/>
<path d="M50.8697 56.9082C50.6077 56.5797 50.6673 56.0966 51.0028 55.829C51.3382 55.5615 51.8226 55.6109 52.0845 55.9394C52.3465 56.2679 52.2869 56.7511 51.9514 57.0186C51.6159 57.2861 51.1316 57.2367 50.8697 56.9082Z" fill="#5E895F"/>
<path d="M44.8537 60.4829C44.6714 60.1044 44.837 59.6466 45.2236 59.4604C45.6102 59.2742 46.0714 59.4302 46.2537 59.8087C46.436 60.1873 46.2703 60.6451 45.8837 60.8312C45.4972 61.0174 45.036 60.8615 44.8537 60.4829Z" fill="#5E895F"/>
<path d="M38.1945 62.6286C38.101 62.219 38.3643 61.8096 38.7826 61.7141C39.201 61.6186 39.6159 61.8732 39.7094 62.2829C39.8029 62.6925 39.5395 63.1019 39.1212 63.1974C38.7029 63.2929 38.288 63.0382 38.1945 62.6286Z" fill="#5E895F"/>
<path d="M31.2237 63.2393C31.2237 62.8191 31.5716 62.4785 32.0007 62.4785C32.4297 62.4785 32.7776 62.8191 32.7776 63.2393C32.7776 63.6594 32.4297 64 32.0007 64C31.5716 64 31.2237 63.6594 31.2237 63.2393Z" fill="#5E895F"/>
<path d="M24.2916 62.2832C24.3851 61.8735 24.8 61.6189 25.2183 61.7144C25.6366 61.8099 25.9 62.2193 25.8065 62.6289C25.713 63.0385 25.2981 63.2932 24.8798 63.1977C24.4614 63.1022 24.1981 62.6928 24.2916 62.2832Z" fill="#5E895F"/>
<path d="M17.7463 59.8082C17.9285 59.4297 18.3897 59.2738 18.7763 59.4599C19.1629 59.6461 19.3285 60.1039 19.1462 60.4824C18.9639 60.861 18.5028 61.0169 18.1162 60.8307C17.7296 60.6446 17.564 60.1868 17.7463 59.8082Z" fill="#5E895F"/>
<path d="M11.9164 55.9393C12.1784 55.6108 12.6627 55.5614 12.9982 55.829C13.3337 56.0965 13.3933 56.5797 13.1313 56.9082C12.8693 57.2366 12.385 57.2861 12.0496 57.0185C11.7141 56.751 11.6545 56.2678 11.9164 55.9393Z" fill="#5E895F"/>
<path d="M7.09104 50.8697C7.41953 50.6078 7.9027 50.6674 8.17023 51.0028C8.43776 51.3383 8.38835 51.8226 8.05987 52.0846C7.73138 52.3465 7.24821 52.2869 6.98068 51.9515C6.71314 51.616 6.76256 51.1317 7.09104 50.8697Z" fill="#5E895F"/>
<path d="M3.51634 44.8534C3.89488 44.6711 4.35267 44.8367 4.53885 45.2233C4.72502 45.6099 4.56908 46.0711 4.19054 46.2534C3.812 46.4357 3.3542 46.27 3.16803 45.8834C2.98185 45.4968 3.1378 45.0357 3.51634 44.8534Z" fill="#5E895F"/>
<path d="M1.36905 38.1939C1.77867 38.1004 2.18813 38.3637 2.28361 38.782C2.37909 39.2004 2.12444 39.6153 1.71482 39.7088C1.3052 39.8023 0.895742 39.5389 0.80026 39.1206C0.704779 38.7023 0.959435 38.2874 1.36905 38.1939Z" fill="#5E895F"/>
<path d="M18.7873 31.7805C20.4352 31.4078 22.0732 32.4415 22.446 34.0894C22.8187 35.7373 21.785 37.3754 20.1371 37.7481C18.4892 38.1209 16.8511 37.0871 16.4784 35.4392C16.1056 33.7913 17.1394 32.1533 18.7873 31.7805Z" fill="#5E895F"/>
<path d="M23.8962 21.4265C25.2151 22.4825 25.4282 24.4077 24.3722 25.7266C23.3162 27.0455 21.391 27.2586 20.0721 26.2026C18.7532 25.1466 18.5401 23.2214 19.5961 21.9025C20.6521 20.5837 22.5773 20.3705 23.8962 21.4265Z" fill="#5E895F"/>
<path d="M35.1766 18.9637C35.1733 20.6532 33.801 22.0202 32.1115 22.0169C30.4219 22.0136 29.055 20.6413 29.0583 18.9518C29.0615 17.2622 30.4338 15.8952 32.1234 15.8985C33.8129 15.9018 35.1799 17.2741 35.1766 18.9637Z" fill="#5E895F"/>
<path d="M44.1326 26.249C42.8096 27.2999 40.8853 27.0792 39.8344 25.7563C38.7836 24.4333 39.0042 22.5089 40.3272 21.4581C41.6502 20.4072 43.5745 20.6278 44.6254 21.9508C45.6762 23.2738 45.4556 25.1982 44.1326 26.249Z" fill="#5E895F"/>
<path d="M44.0254 37.7941C42.379 37.4149 41.3516 35.7728 41.7308 34.1264C42.11 32.4799 43.752 31.4526 45.3985 31.8318C47.0449 32.2109 48.0723 33.853 47.6931 35.4995C47.3139 37.1459 45.6719 38.1732 44.0254 37.7941Z" fill="#5E895F"/>
<path d="M34.9297 44.9061C34.1996 43.3825 34.8429 41.5554 36.3666 40.8253C37.8902 40.0952 39.7172 40.7385 40.4473 42.2622C41.1774 43.7858 40.5341 45.6129 39.0105 46.343C37.4868 47.0731 35.6598 46.4298 34.9297 44.9061Z" fill="#5E895F"/>
<path d="M23.697 42.2301C24.433 40.7093 26.2625 40.0731 27.7833 40.8091C29.3041 41.5452 29.9403 43.3747 29.2043 44.8955C28.4682 46.4163 26.6387 47.0524 25.1179 46.3164C23.5972 45.5804 22.961 43.7509 23.697 42.2301Z" fill="#5E895F"/>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,4 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="15" y="12" width="2" height="18" rx="1" fill="black"/>
<circle cx="16" cy="13" r="2" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 212 B

View File

@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="15" y="12" width="2" height="18" rx="1" fill="black"/>
<circle cx="16" cy="13" r="2" fill="black"/>
<path d="M18.9565 17.0346C19.2823 17.4793 19.9143 17.5806 20.2989 17.1857C20.936 16.5313 21.4186 15.7367 21.7044 14.86C22.094 13.6653 22.0986 12.3784 21.7176 11.181C21.3366 9.9835 20.5893 8.93586 19.5811 8.18586C18.5728 7.43586 17.3545 7.02134 16.0981 7.0008C14.8416 6.98026 13.6105 7.35472 12.5782 8.07136C11.546 8.788 10.7648 9.81065 10.3449 10.995C9.92499 12.1794 9.88754 13.4657 10.2378 14.6725C10.4949 15.5581 10.9512 16.368 11.5666 17.0429C11.938 17.4502 12.573 17.3696 12.9132 16.9358V16.9358C13.2534 16.5021 13.1647 15.8801 12.8283 15.4434C12.5246 15.0493 12.2952 14.5996 12.1549 14.1161C11.9212 13.3108 11.9462 12.4524 12.2264 11.6621C12.5066 10.8718 13.0279 10.1893 13.7167 9.71113C14.4055 9.23292 15.227 8.98304 16.0655 8.99675C16.9039 9.01046 17.7168 9.28706 18.3896 9.78754C19.0624 10.288 19.5611 10.9871 19.8154 11.7862C20.0696 12.5852 20.0665 13.444 19.8065 14.2412C19.6505 14.7198 19.4066 15.1618 19.0902 15.5458C18.7397 15.9713 18.6307 16.59 18.9565 17.0346V17.0346Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,6 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="15" y="12" width="2" height="18" rx="1" fill="black"/>
<circle cx="16" cy="13" r="2" fill="black"/>
<path d="M18.9565 17.0346C19.2823 17.4793 19.9143 17.5806 20.2989 17.1857C20.936 16.5313 21.4186 15.7367 21.7044 14.86C22.094 13.6653 22.0986 12.3784 21.7176 11.181C21.3366 9.9835 20.5893 8.93586 19.5811 8.18586C18.5728 7.43586 17.3545 7.02134 16.0981 7.0008C14.8416 6.98026 13.6105 7.35472 12.5782 8.07136C11.546 8.788 10.7648 9.81065 10.3449 10.995C9.92499 12.1794 9.88754 13.4657 10.2378 14.6725C10.4949 15.5581 10.9512 16.368 11.5666 17.0429C11.938 17.4502 12.573 17.3696 12.9132 16.9358V16.9358C13.2534 16.5021 13.1647 15.8801 12.8283 15.4434C12.5246 15.0493 12.2952 14.5996 12.1549 14.1161C11.9212 13.3108 11.9462 12.4524 12.2264 11.6621C12.5066 10.8718 13.0279 10.1893 13.7167 9.71113C14.4055 9.23292 15.227 8.98304 16.0655 8.99675C16.9039 9.01046 17.7168 9.28706 18.3896 9.78754C19.0624 10.288 19.5611 10.9871 19.8154 11.7862C20.0696 12.5852 20.0665 13.444 19.8065 14.2412C19.6505 14.7198 19.4066 15.1618 19.0902 15.5458C18.7397 15.9713 18.6307 16.59 18.9565 17.0346V17.0346Z" fill="black"/>
<path d="M19.0228 21.4701C19.2097 21.9937 19.7885 22.2713 20.2907 22.0327C22.0904 21.1779 23.6012 19.8009 24.6192 18.0705C25.808 16.0496 26.2489 13.6753 25.865 11.3624C25.4811 9.0495 24.2966 6.94502 22.5187 5.41668C20.7407 3.88833 18.4823 3.03329 16.1379 3.00095C13.7936 2.96861 11.5124 3.76103 9.69296 5.23976C7.87352 6.71849 6.63151 8.7895 6.18392 11.0909C5.73632 13.3924 6.11162 15.7779 7.24423 17.8308C8.21408 19.5886 9.6864 21.0068 11.4618 21.9109C11.9572 22.1632 12.5435 21.9018 12.7447 21.3835V21.3835C12.946 20.8652 12.6849 20.2874 12.1961 20.0226C10.8583 19.298 9.74873 18.2025 9.00706 16.8582C8.10249 15.2186 7.80275 13.3134 8.16022 11.4753C8.5177 9.63721 9.50966 7.98317 10.9628 6.80216C12.4159 5.62115 14.2378 4.98827 16.1102 5.0141C17.9825 5.03992 19.7863 5.72281 21.2063 6.94345C22.6262 8.1641 23.5722 9.84487 23.8788 11.6921C24.1855 13.5394 23.8333 15.4356 22.8839 17.0496C22.1054 18.3729 20.966 19.4374 19.6088 20.1249C19.1128 20.3761 18.8359 20.9465 19.0228 21.4701V21.4701Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,3 @@
<svg width="32" height="32" fill="none" version="1.1" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path d="m26.5 18.5c0 0.2967-0.088 0.5867-0.2528 0.8334-0.1648 0.2466-0.3991 0.4389-0.6732 0.5524s-0.5757 0.1433-0.8666 0.0854c-0.291-0.0579-0.5583-0.2008-0.7681-0.4105-0.2097-0.2098-0.3526-0.4771-0.4105-0.7681-0.0579-0.2909-0.0281-0.5925 0.0854-0.8666s0.3058-0.5084 0.5524-0.6732c0.2467-0.1648 0.5367-0.2528 0.8334-0.2528 0.3978 0 0.7794 0.158 1.0607 0.4393s0.4393 0.6629 0.4393 1.0607zm4.5 0c0 5.0462-2.635 9-6 9h-18c-3.365 0-6-3.9538-6-9 0-5.0462 2.635-9 6-9h4.5863l4.7062-4.7075c0.0929-0.092834 0.2033-0.16645 0.3246-0.21664 0.1214-0.050188 0.2515-0.075967 0.3829-0.075864h4c0.2652 0 0.5196 0.10536 0.7071 0.29289 0.1875 0.18754 0.2929 0.44189 0.2929 0.70711s-0.1054 0.51957-0.2929 0.70711c-0.1875 0.18753-0.4419 0.29289-0.7071 0.29289h-3.5863l-3 3h10.586c3.365 0 6 3.9537 6 9zm-24 7h14.189c-0.7096-0.893-1.2467-1.9103-1.5837-3h-9.605c-0.26522 0-0.51957-0.1054-0.70711-0.2929-0.18753-0.1875-0.29289-0.4419-0.29289-0.7071s0.10536-0.5196 0.29289-0.7071c0.18754-0.1875 0.44189-0.2929 0.70711-0.2929h9.145c-0.0967-0.6623-0.1451-1.3307-0.145-2 0-2.8575 0.845-5.3625 2.1887-7h-14.189c-1.5588 0-2.9438 1.6575-3.6 4h9.6c0.2652 0 0.5196 0.1054 0.7071 0.2929s0.2929 0.4419 0.2929 0.7071-0.1054 0.5196-0.2929 0.7071-0.4419 0.2929-0.7071 0.2929h-9.9562c-0.02833 0.3275-0.04292 0.6608-0.04375 1 0 3.795 1.8312 7 4 7zm22-7c0-3.795-1.8312-7-4-7s-4 3.205-4 7 1.8312 7 4 7 4-3.205 4-7z" fill="#8f7676"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,3 @@
<svg width="32" height="32" fill="none" version="1.1" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path d="m27 9.24h-4v-4.16c0-0.55166-0.21071-1.0807-0.58576-1.4708-0.37505-0.39006-0.88378-0.60921-1.4142-0.60921h-16c-0.53043 0-1.0391 0.21915-1.4142 0.60921-0.37508 0.39008-0.58579 0.91913-0.58579 1.4708v16.64c5.85e-4 0.19565 0.054232 0.38718 0.15478 0.5525 0.10054 0.16542 0.2439 0.29792 0.41362 0.38242 0.1697 0.08439 0.35889 0.11732 0.54578 0.09501 0.18691-0.022319 0.36397-0.099019 0.51082-0.22133l4.375-3.6686v3.9c0 0.55163 0.21072 1.0807 0.5858 1.4707 0.37505 0.39011 0.88378 0.60927 1.4142 0.60927h11.699l4.6762 3.9286c0.17691 0.14885 0.39737 0.23042 0.62498 0.2314 0.2652 0 0.51957-0.10952 0.70709-0.30463 0.18752-0.195 0.29293-0.45955 0.29293-0.73537v-16.64c0-0.55163-0.21071-1.0807-0.58576-1.4707-0.37505-0.39012-0.88378-0.60927-1.4143-0.60927zm-18.681 7.5114-3.3188 2.7911v-14.462h16v11.44h-12.053c-0.22886 0-0.45081 0.08168-0.62874 0.2314zm18.681 9.0311-3.3187-2.7911c-0.17691-0.14885-0.39748-0.23053-0.62498-0.2314h-12.056v-4.16h10c0.5304 0 1.0391-0.21916 1.4142-0.60927 0.37505-0.39 0.58576-0.9191 0.58576-1.4707v-5.2h4z" fill="#000" stroke-width="1.0833"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,5 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#7d7d8e" viewBox="0 0 448 512"><path d="M448 80v48c0 44.2-100.3 80-224 80S0 172.2 0 128V80C0 35.8 100.3 0 224 0S448 35.8 448 80zM393.2 214.7c20.8-7.4 39.9-16.9 54.8-28.6V288c0 44.2-100.3 80-224 80S0 332.2 0 288V186.1c14.9 11.8 34 21.2 54.8 28.6C99.7 230.7 159.5 240 224 240s124.3-9.3 169.2-25.3zM0 346.1c14.9 11.8 34 21.2 54.8 28.6C99.7 390.7 159.5 400 224 400s124.3-9.3 169.2-25.3c20.8-7.4 39.9-16.9 54.8-28.6V432c0 44.2-100.3 80-224 80S0 476.2 0 432V346.1z"/></svg> <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- <path d="M16 3C9.27125 3 4 6.075 4 10V22C4 25.925 9.27125 29 16 29C22.7288 29 28 25.925 28 22V10C28 6.075 22.7288 3 16 3ZM26 16C26 17.2025 25.015 18.4287 23.2987 19.365C21.3662 20.4187 18.7738 21 16 21C13.2262 21 10.6338 20.4187 8.70125 19.365C6.985 18.4287 6 17.2025 6 16V13.92C8.1325 15.795 11.7787 17 16 17C20.2213 17 23.8675 15.79 26 13.92V16ZM8.70125 6.635C10.6338 5.58125 13.2262 5 16 5C18.7738 5 21.3662 5.58125 23.2987 6.635C25.015 7.57125 26 8.7975 26 10C26 11.2025 25.015 12.4287 23.2987 13.365C21.3662 14.4187 18.7738 15 16 15C13.2262 15 10.6338 14.4187 8.70125 13.365C6.985 12.4287 6 11.2025 6 10C6 8.7975 6.985 7.57125 8.70125 6.635ZM23.2987 25.365C21.3662 26.4187 18.7738 27 16 27C13.2262 27 10.6338 26.4187 8.70125 25.365C6.985 24.4287 6 23.2025 6 22V19.92C8.1325 21.795 11.7787 23 16 23C20.2213 23 23.8675 21.79 26 19.92V22C26 23.2025 25.015 24.4287 23.2987 25.365Z" fill="black"/>
Font Awesome Free 5.2.0 by @fontawesome - https://fontawesome.com </svg>
License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
-->

Before

Width:  |  Height:  |  Size: 689 B

After

Width:  |  Height:  |  Size: 1001 B

View File

@ -0,0 +1,3 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M25.9996 34.9998C25.9996 35.5931 25.8236 36.1731 25.494 36.6665C25.1644 37.1598 24.6958 37.5444 24.1476 37.7714C23.5995 37.9985 22.9963 38.0579 22.4143 37.9421C21.8324 37.8264 21.2978 37.5407 20.8783 37.1211C20.4587 36.7015 20.173 36.167 20.0572 35.5851C19.9415 35.0031 20.0009 34.3999 20.228 33.8517C20.455 33.3036 20.8395 32.835 21.3329 32.5054C21.8262 32.1757 22.4062 31.9998 22.9996 31.9998C23.7952 31.9998 24.5583 32.3159 25.1209 32.8785C25.6835 33.4411 25.9996 34.2041 25.9996 34.9998ZM40.9996 31.9998C40.4062 31.9998 39.8262 32.1757 39.3329 32.5054C38.8395 32.835 38.455 33.3036 38.228 33.8517C38.0009 34.3999 37.9415 35.0031 38.0572 35.5851C38.173 36.167 38.4587 36.7015 38.8783 37.1211C39.2978 37.5407 39.8324 37.8264 40.4143 37.9421C40.9963 38.0579 41.5995 37.9985 42.1476 37.7714C42.6958 37.5444 43.1644 37.1598 43.494 36.6665C43.8236 36.1731 43.9996 35.5931 43.9996 34.9998C43.9996 34.2041 43.6835 33.4411 43.1209 32.8785C42.5583 32.3159 41.7952 31.9998 40.9996 31.9998ZM59.6121 48.2248L42.8621 55.6523C42.3584 55.8762 41.8143 55.9947 41.2632 56.0006C40.712 56.0065 40.1655 55.8997 39.6572 55.6866C39.1488 55.4735 38.6894 55.1588 38.3072 54.7616C37.925 54.3645 37.6281 53.8934 37.4346 53.3773L35.4071 47.8773C34.2871 47.9523 33.1513 47.9906 31.9996 47.9923C30.8479 47.994 29.7121 47.9556 28.5921 47.8773L26.5646 53.3773C26.3708 53.8932 26.0737 54.364 25.6914 54.761C25.3092 55.1579 24.8498 55.4725 24.3416 55.6855C23.8333 55.8986 23.2869 56.0055 22.7359 55.9998C22.1849 55.9941 21.6408 55.8758 21.1371 55.6523L4.38709 48.2248C3.52256 47.8468 2.82032 47.1741 2.4055 46.3266C1.99069 45.4792 1.89025 44.5119 2.12209 43.5973L9.49959 14.4998C9.69429 13.7431 10.1054 13.0595 10.6827 12.5329C11.2599 12.0063 11.9782 11.6594 12.7496 11.5348L21.7646 10.0523C22.7584 9.89244 23.7761 10.1079 24.6199 10.6569C25.4636 11.2058 26.073 12.0489 26.3296 13.0223L27.1446 16.2323C28.7329 16.0773 30.3513 15.9998 31.9996 15.9998C33.6479 15.9998 35.2646 16.0773 36.8496 16.2323L37.6646 13.0223C37.9206 12.0486 38.5299 11.2052 39.3738 10.6561C40.2177 10.1071 41.2357 9.89186 42.2296 10.0523L51.2496 11.5348C52.021 11.6594 52.7392 12.0063 53.3165 12.5329C53.8937 13.0595 54.3049 13.7431 54.4996 14.4998L61.8821 43.5948C62.1142 44.5104 62.0134 45.4787 61.5976 46.3268C61.1818 47.1749 60.4781 47.8476 59.6121 48.2248ZM57.9996 44.5698L50.6171 15.4998C50.6171 15.4998 50.6171 15.4998 50.5971 15.4998L41.5821 13.9998C41.5756 13.9961 41.5683 13.9941 41.5608 13.9941C41.5534 13.9941 41.5461 13.9961 41.5396 13.9998L40.8321 16.7848C42.0821 17.0198 43.3321 17.2998 44.5396 17.6398C45.0156 17.7611 45.4304 18.0535 45.7047 18.461C45.9791 18.8686 46.0938 19.3628 46.027 19.8496C45.9603 20.3363 45.7167 20.7814 45.3428 21.1001C44.9689 21.4187 44.4907 21.5886 43.9996 21.5773C43.817 21.5768 43.6354 21.5515 43.4596 21.5023C39.7245 20.4887 35.8698 19.9834 31.9996 19.9998C28.1295 19.9825 24.2748 20.4871 20.5396 21.4998C20.2844 21.5787 20.0161 21.6056 19.7503 21.5791C19.4845 21.5526 19.2268 21.4731 18.9922 21.3454C18.7577 21.2176 18.5511 21.0442 18.3847 20.8353C18.2182 20.6264 18.0953 20.3864 18.0232 20.1292C17.9511 19.872 17.9312 19.6031 17.9647 19.3381C17.9982 19.0731 18.0844 18.8175 18.2183 18.5864C18.3522 18.3554 18.531 18.1534 18.7442 17.9925C18.9574 17.8317 19.2006 17.7151 19.4596 17.6498C20.6646 17.3098 21.9046 17.0298 23.1646 16.7948L22.4571 13.9998C22.4571 13.9998 22.4571 13.9998 22.4271 13.9998L13.4021 15.4823C13.3947 15.4803 13.387 15.4803 13.3796 15.4823L5.99959 44.5823L22.7496 51.9998C22.758 52.0044 22.7675 52.0068 22.7771 52.0068C22.7867 52.0068 22.7962 52.0044 22.8046 51.9998L24.4996 47.4298C22.8013 47.17 21.12 46.8094 19.4646 46.3498C18.9715 46.1922 18.5587 45.8497 18.313 45.3941C18.0673 44.9385 18.0078 44.4054 18.147 43.9068C18.2862 43.4082 18.6133 42.9831 19.0595 42.7207C19.5057 42.4583 20.0362 42.3791 20.5396 42.4998C24.2746 43.5137 28.1295 44.0182 31.9996 43.9998C35.8697 44.0182 39.7246 43.5137 43.4596 42.4998C43.9705 42.3566 44.5173 42.4222 44.9798 42.6821C45.4423 42.9421 45.7826 43.3752 45.9258 43.886C46.0691 44.3969 46.0035 44.9438 45.7435 45.4063C45.4835 45.8688 45.0505 46.2091 44.5396 46.3523C42.8825 46.8112 41.1995 47.171 39.4996 47.4298L41.1871 51.9998C41.1951 52.0042 41.2042 52.0065 41.2133 52.0065C41.2225 52.0065 41.2315 52.0042 41.2396 51.9998L57.9996 44.5698Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -1,5 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#7d7d8e" viewBox="0 0 576 512"><path d="M402.6 83.2l90.2 90.2c3.8 3.8 3.8 10 0 13.8L274.4 405.6l-92.8 10.3c-12.4 1.4-22.9-9.1-21.5-21.5l10.3-92.8L388.8 83.2c3.8-3.8 10-3.8 13.8 0zm162-22.9l-48.8-48.8c-15.2-15.2-39.9-15.2-55.2 0l-35.4 35.4c-3.8 3.8-3.8 10 0 13.8l90.2 90.2c3.8 3.8 10 3.8 13.8 0l35.4-35.4c15.2-15.3 15.2-40 0-55.2zM384 346.2V448H64V128h229.8c3.2 0 6.2-1.3 8.5-3.5l40-40c7.6-7.6 2.2-20.5-8.5-20.5H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V306.2c0-10.7-12.9-16-20.5-8.5l-40 40c-2.2 2.3-3.5 5.3-3.5 8.5z"/></svg> <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- <path d="M28.4138 9.17125L22.8288 3.585C22.643 3.39924 22.4225 3.25188 22.1799 3.15134C21.9372 3.0508 21.6771 2.99905 21.4144 2.99905C21.1517 2.99905 20.8916 3.0508 20.6489 3.15134C20.4062 3.25188 20.1857 3.39924 20 3.585L4.58626 19C4.39973 19.185 4.25185 19.4053 4.15121 19.648C4.05057 19.8907 3.99917 20.151 4.00001 20.4138V26C4.00001 26.5304 4.21072 27.0391 4.5858 27.4142C4.96087 27.7893 5.46958 28 6.00001 28H11.5863C11.849 28.0008 12.1093 27.9494 12.352 27.8488C12.5947 27.7482 12.815 27.6003 13 27.4138L28.4138 12C28.5995 11.8143 28.7469 11.5938 28.8474 11.3511C28.948 11.1084 28.9997 10.8483 28.9997 10.5856C28.9997 10.3229 28.948 10.0628 28.8474 9.82015C28.7469 9.57747 28.5995 9.35698 28.4138 9.17125ZM6.41376 20L17 9.41375L19.0863 11.5L8.50001 22.085L6.41376 20ZM6.00001 22.4138L9.58626 26H6.00001V22.4138ZM12 25.5863L9.91376 23.5L20.5 12.9138L22.5863 15L12 25.5863ZM24 13.5863L18.4138 8L21.4138 5L27 10.585L24 13.5863Z" fill="black"/>
Font Awesome Free 5.2.0 by @fontawesome - https://fontawesome.com </svg>
License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
-->

Before

Width:  |  Height:  |  Size: 778 B

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M224,48H32a8,8,0,0,0-8,8V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A8,8,0,0,0,224,48ZM203.43,64,128,133.15,52.57,64ZM216,192H40V74.19l82.59,75.71a8,8,0,0,0,10.82,0L216,74.19V192Z"></path></svg>

After

Width:  |  Height:  |  Size: 306 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40v72a8,8,0,0,0,16,0V40h88V88a8,8,0,0,0,8,8h48V224a8,8,0,0,0,16,0V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM144,144H128a8,8,0,0,0-8,8v56a8,8,0,0,0,8,8h16a36,36,0,0,0,0-72Zm0,56h-8V160h8a20,20,0,0,1,0,40Zm-40-48v56a8,8,0,0,1-16,0V177.38L74.55,196.59a8,8,0,0,1-13.1,0L48,177.38V208a8,8,0,0,1-16,0V152a8,8,0,0,1,14.55-4.59L68,178.05l21.45-30.64A8,8,0,0,1,104,152Z"></path></svg>

After

Width:  |  Height:  |  Size: 550 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M224,152a8,8,0,0,1-8,8H192v16h16a8,8,0,0,1,0,16H192v16a8,8,0,0,1-16,0V152a8,8,0,0,1,8-8h32A8,8,0,0,1,224,152ZM92,172a28,28,0,0,1-28,28H56v8a8,8,0,0,1-16,0V152a8,8,0,0,1,8-8H64A28,28,0,0,1,92,172Zm-16,0a12,12,0,0,0-12-12H56v24h8A12,12,0,0,0,76,172Zm88,8a36,36,0,0,1-36,36H112a8,8,0,0,1-8-8V152a8,8,0,0,1,8-8h16A36,36,0,0,1,164,180Zm-16,0a20,20,0,0,0-20-20h-8v40h8A20,20,0,0,0,148,180ZM40,112V40A16,16,0,0,1,56,24h96a8,8,0,0,1,5.66,2.34l56,56A8,8,0,0,1,216,88v24a8,8,0,0,1-16,0V96H152a8,8,0,0,1-8-8V40H56v72a8,8,0,0,1-16,0ZM160,80h28.69L160,51.31Z"></path></svg>

After

Width:  |  Height:  |  Size: 669 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M48,120a8,8,0,0,0,8-8V40h88V88a8,8,0,0,0,8,8h48v16a8,8,0,0,0,16,0V88a8,8,0,0,0-2.34-5.66l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40v72A8,8,0,0,0,48,120ZM160,51.31,188.69,80H160Zm-5.49,105.34L137.83,180l16.68,23.35a8,8,0,0,1-13,9.3L128,193.76l-13.49,18.89a8,8,0,1,1-13-9.3L118.17,180l-16.68-23.35a8,8,0,1,1,13-9.3L128,166.24l13.49-18.89a8,8,0,0,1,13,9.3ZM92,152a8,8,0,0,1-8,8H72v48a8,8,0,0,1-16,0V160H44a8,8,0,0,1,0-16H84A8,8,0,0,1,92,152Zm128,0a8,8,0,0,1-8,8H200v48a8,8,0,0,1-16,0V160H172a8,8,0,0,1,0-16h40A8,8,0,0,1,220,152Z"></path></svg>

After

Width:  |  Height:  |  Size: 651 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Z"></path></svg>

After

Width:  |  Height:  |  Size: 320 B

View File

@ -0,0 +1,10 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_291_4849)">
<path d="M52.0775 18.92C52.6888 16.948 52.8849 14.8707 52.6534 12.8192C52.4219 10.7676 51.7679 8.78624 50.7325 7.00003C50.5569 6.69593 50.3044 6.44341 50.0003 6.26787C49.6961 6.09233 49.3511 5.99995 49 6.00003C46.6705 5.99516 44.3721 6.53519 42.2885 7.57699C40.2049 8.6188 38.3938 10.1335 37 12H31C29.6062 10.1335 27.7951 8.6188 25.7115 7.57699C23.6279 6.53519 21.3295 5.99516 19 6.00003C18.6489 5.99995 18.3039 6.09233 17.9997 6.26787C17.6956 6.44341 17.4431 6.69593 17.2675 7.00003C16.2321 8.78624 15.5781 10.7676 15.3466 12.8192C15.1151 14.8707 15.3112 16.948 15.9225 18.92C14.6868 21.0768 14.0249 23.5145 14 26V28C14.0042 31.384 15.2327 34.6523 17.4586 37.2012C19.6844 39.7501 22.7574 41.4076 26.11 41.8675C24.7415 43.6187 23.9987 45.7776 24 48V50H18C16.4087 50 14.8826 49.3679 13.7574 48.2427C12.6321 47.1175 12 45.5913 12 44C12 42.6868 11.7413 41.3865 11.2388 40.1732C10.7362 38.9599 9.99965 37.8576 9.07107 36.929C8.14248 36.0004 7.04009 35.2638 5.82683 34.7612C4.61358 34.2587 3.31322 34 2 34C1.46957 34 0.960859 34.2107 0.585786 34.5858C0.210714 34.9609 0 35.4696 0 36C0 36.5305 0.210714 37.0392 0.585786 37.4143C0.960859 37.7893 1.46957 38 2 38C3.5913 38 5.11742 38.6322 6.24264 39.7574C7.36786 40.8826 8 42.4087 8 44C8 46.6522 9.05357 49.1957 10.9289 51.0711C12.8043 52.9465 15.3478 54 18 54H24V58C24 58.5305 24.2107 59.0392 24.5858 59.4143C24.9609 59.7893 25.4696 60 26 60C26.5304 60 27.0391 59.7893 27.4142 59.4143C27.7893 59.0392 28 58.5305 28 58V48C28 46.4087 28.6321 44.8826 29.7574 43.7574C30.8826 42.6322 32.4087 42 34 42C35.5913 42 37.1174 42.6322 38.2426 43.7574C39.3679 44.8826 40 46.4087 40 48V58C40 58.5305 40.2107 59.0392 40.5858 59.4143C40.9609 59.7893 41.4696 60 42 60C42.5304 60 43.0391 59.7893 43.4142 59.4143C43.7893 59.0392 44 58.5305 44 58V48C44.0013 45.7776 43.2585 43.6187 41.89 41.8675C45.2426 41.4076 48.3156 39.7501 50.5414 37.2012C52.7673 34.6523 53.9958 31.384 54 28V26C53.9751 23.5145 53.3132 21.0768 52.0775 18.92ZM50 28C50 30.6522 48.9464 33.1957 47.0711 35.0711C45.1957 36.9465 42.6522 38 40 38H28C25.3478 38 22.8043 36.9465 20.9289 35.0711C19.0536 33.1957 18 30.6522 18 28V26C18.0245 24.0001 18.6233 22.0493 19.725 20.38C19.9304 20.1093 20.0634 19.7908 20.1115 19.4544C20.1596 19.118 20.1212 18.775 20 18.4575C19.4791 17.114 19.2283 15.6809 19.2622 14.2404C19.2961 12.7998 19.614 11.3801 20.1975 10.0625C21.8343 10.2386 23.4105 10.7808 24.8092 11.649C26.2079 12.5171 27.3933 13.6889 28.2775 15.0775C28.4577 15.3593 28.7056 15.5914 28.9987 15.7525C29.2918 15.9137 29.6206 15.9988 29.955 16H38.0425C38.3782 16 38.7085 15.9155 39.003 15.7543C39.2975 15.5931 39.5466 15.3604 39.7275 15.0775C40.6116 13.6888 41.7969 12.5169 43.1957 11.6487C44.5944 10.7806 46.1707 10.2384 47.8075 10.0625C48.3902 11.3804 48.7072 12.8003 48.7402 14.2409C48.7733 15.6814 48.5217 17.1144 48 18.4575C47.8791 18.7719 47.8387 19.1115 47.8823 19.4455C47.926 19.7795 48.0524 20.0973 48.25 20.37C49.3626 22.0393 49.9703 23.9941 50 26V28Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_291_4849">
<rect width="64" height="64" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M32 6C26.8577 6 21.8309 7.52487 17.5552 10.3818C13.2795 13.2387 9.94702 17.2994 7.97914 22.0502C6.01127 26.8011 5.49638 32.0288 6.49959 37.0723C7.50281 42.1159 9.97907 46.7486 13.6152 50.3848C17.2514 54.0209 21.8842 56.4972 26.9277 57.5004C31.9712 58.5036 37.1989 57.9887 41.9498 56.0209C46.7007 54.053 50.7613 50.7205 53.6182 46.4448C56.4751 42.1691 58 37.1423 58 32C57.9921 25.1068 55.2502 18.4982 50.376 13.624C45.5018 8.74977 38.8932 6.00794 32 6ZM54 32C54.0018 34.0289 53.7216 36.0482 53.1675 38H43.54C44.1534 34.0235 44.1534 29.9765 43.54 26H53.1675C53.7216 27.9518 54.0018 29.9711 54 32ZM25.5 42H38.5C37.2193 46.1965 34.996 50.0445 32 53.25C29.0052 50.0437 26.782 46.1959 25.5 42ZM24.525 38C23.8384 34.0295 23.8384 29.9705 24.525 26H39.495C40.1817 29.9705 40.1817 34.0295 39.495 38H24.525ZM10 32C9.99827 29.9711 10.2785 27.9518 10.8325 26H20.46C19.8467 29.9765 19.8467 34.0235 20.46 38H10.8325C10.2785 36.0482 9.99827 34.0289 10 32ZM38.5 22H25.5C26.7807 17.8035 29.0041 13.9555 32 10.75C34.9948 13.9563 37.218 17.8041 38.5 22ZM51.5825 22H42.6775C41.5551 17.8823 39.6635 14.0142 37.1025 10.6C40.1968 11.3433 43.0937 12.7469 45.5948 14.7146C48.0958 16.6823 50.1418 19.1676 51.5925 22H51.5825ZM26.8975 10.6C24.3365 14.0142 22.4449 17.8823 21.3225 22H12.4075C13.8582 19.1676 15.9042 16.6823 18.4052 14.7146C20.9063 12.7469 23.8032 11.3433 26.8975 10.6ZM12.4075 42H21.3225C22.4449 46.1177 24.3365 49.9858 26.8975 53.4C23.8032 52.6567 20.9063 51.2531 18.4052 49.2854C15.9042 47.3177 13.8582 44.8324 12.4075 42ZM37.1025 53.4C39.6635 49.9858 41.5551 46.1177 42.6775 42H51.5925C50.1418 44.8324 48.0958 47.3177 45.5948 49.2854C43.0937 51.2531 40.1968 52.6567 37.1025 53.4Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,3 @@
<svg width="64" height="64" fill="none" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<path d="m55.677 28.017-20.745-20.8c-0.77801-0.77955-1.8329-1.2175-2.9328-1.2175-1.0999 0-2.1548 0.43791-2.9328 1.2175l-20.745 20.8c-0.38712 0.38524-0.69392 0.84384-0.9027 1.3491-0.20888 0.50528-0.31532 1.0471-0.31344 1.594v24.96c0 0.55164 0.21862 1.0807 0.60763 1.4708 0.389 0.39 0.91664 0.60917 1.4668 0.60917h16.596c0.5502 0 1.0778-0.21917 1.4668-0.60917 0.38912-0.39011 0.60762-0.91918 0.60762-1.4708v-14.56h8.2979v14.56c0 0.55164 0.21851 1.0807 0.60762 1.4708 0.389 0.39 0.91664 0.60917 1.4668 0.60917h16.596c0.5502 0 1.0778-0.21917 1.4668-0.60917 0.389-0.39011 0.60762-0.91918 0.60762-1.4708v-24.96c0.0019-0.54688-0.10455-1.0887-0.31333-1.594-0.20888-0.50528-0.51568-0.96388-0.90281-1.3491zm-2.9328 25.823h-12.447v-14.56c0-0.55164-0.21851-1.0807-0.60763-1.4707-0.389-0.39011-0.91664-0.60928-1.4668-0.60928h-12.447c-0.5502 0-1.0778 0.21918-1.4668 0.60928-0.389 0.39-0.60763 0.91907-0.60763 1.4707v14.56h-12.447v-22.88l20.745-20.8 20.745 20.8z" fill="#000" stroke-width="1.1064"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 3C13.4288 3 10.9154 3.76244 8.77759 5.1909C6.63975 6.61935 4.97351 8.64968 3.98957 11.0251C3.00563 13.4006 2.74819 16.0144 3.2498 18.5362C3.75141 21.0579 4.98953 23.3743 6.80762 25.1924C8.6257 27.0105 10.9421 28.2486 13.4638 28.7502C15.9856 29.2518 18.5995 28.9944 20.9749 28.0104C23.3503 27.0265 25.3807 25.3603 26.8091 23.2224C28.2376 21.0846 29 18.5712 29 16C28.9964 12.5533 27.6256 9.24882 25.1884 6.81163C22.7512 4.37445 19.4467 3.00364 16 3ZM16 27C13.8244 27 11.6977 26.3549 9.88873 25.1462C8.07979 23.9375 6.66989 22.2195 5.83733 20.2095C5.00477 18.1995 4.78693 15.9878 5.21137 13.854C5.63581 11.7202 6.68345 9.7602 8.22183 8.22183C9.76021 6.68345 11.7202 5.6358 13.854 5.21136C15.9878 4.78692 18.1995 5.00476 20.2095 5.83733C22.2195 6.66989 23.9375 8.07979 25.1462 9.88873C26.3549 11.6977 27 13.8244 27 16C26.9967 18.9164 25.8367 21.7123 23.7745 23.7745C21.7123 25.8367 18.9164 26.9967 16 27ZM18 22C18 22.2652 17.8946 22.5196 17.7071 22.7071C17.5196 22.8946 17.2652 23 17 23C16.4696 23 15.9609 22.7893 15.5858 22.4142C15.2107 22.0391 15 21.5304 15 21V16C14.7348 16 14.4804 15.8946 14.2929 15.7071C14.1054 15.5196 14 15.2652 14 15C14 14.7348 14.1054 14.4804 14.2929 14.2929C14.4804 14.1054 14.7348 14 15 14C15.5304 14 16.0391 14.2107 16.4142 14.5858C16.7893 14.9609 17 15.4696 17 16V21C17.2652 21 17.5196 21.1054 17.7071 21.2929C17.8946 21.4804 18 21.7348 18 22ZM14 10.5C14 10.2033 14.088 9.91332 14.2528 9.66665C14.4176 9.41997 14.6519 9.22771 14.926 9.11418C15.2001 9.00065 15.5017 8.97094 15.7926 9.02882C16.0836 9.0867 16.3509 9.22956 16.5607 9.43934C16.7704 9.64912 16.9133 9.91639 16.9712 10.2074C17.0291 10.4983 16.9994 10.7999 16.8858 11.074C16.7723 11.3481 16.58 11.5824 16.3334 11.7472C16.0867 11.912 15.7967 12 15.5 12C15.1022 12 14.7206 11.842 14.4393 11.5607C14.158 11.2794 14 10.8978 14 10.5Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M26.7075 8.2925L21.7075 3.2925C21.6146 3.19967 21.5042 3.12605 21.3829 3.07586C21.2615 3.02568 21.1314 2.9999 21 3H11C10.4696 3 9.96086 3.21071 9.58579 3.58579C9.21071 3.96086 9 4.46957 9 5V7H7C6.46957 7 5.96086 7.21071 5.58579 7.58579C5.21071 7.96086 5 8.46957 5 9V27C5 27.5304 5.21071 28.0391 5.58579 28.4142C5.96086 28.7893 6.46957 29 7 29H21C21.5304 29 22.0391 28.7893 22.4142 28.4142C22.7893 28.0391 23 27.5304 23 27V25H25C25.5304 25 26.0391 24.7893 26.4142 24.4142C26.7893 24.0391 27 23.5304 27 23V9C27.0001 8.86864 26.9743 8.73855 26.9241 8.61715C26.8739 8.49576 26.8003 8.38544 26.7075 8.2925ZM21 27H7V9H16.5863L21 13.4137V23.98C21 23.9875 21 23.9937 21 24C21 24.0063 21 24.0125 21 24.02V27ZM25 23H23V13C23.0001 12.8686 22.9743 12.7385 22.9241 12.6172C22.8739 12.4958 22.8003 12.3854 22.7075 12.2925L17.7075 7.2925C17.6146 7.19967 17.5042 7.12605 17.3829 7.07586C17.2615 7.02568 17.1314 6.9999 17 7H11V5H20.5863L25 9.41375V23ZM18 19C18 19.2652 17.8946 19.5196 17.7071 19.7071C17.5196 19.8946 17.2652 20 17 20H11C10.7348 20 10.4804 19.8946 10.2929 19.7071C10.1054 19.5196 10 19.2652 10 19C10 18.7348 10.1054 18.4804 10.2929 18.2929C10.4804 18.1054 10.7348 18 11 18H17C17.2652 18 17.5196 18.1054 17.7071 18.2929C17.8946 18.4804 18 18.7348 18 19ZM18 23C18 23.2652 17.8946 23.5196 17.7071 23.7071C17.5196 23.8946 17.2652 24 17 24H11C10.7348 24 10.4804 23.8946 10.2929 23.7071C10.1054 23.5196 10 23.2652 10 23C10 22.7348 10.1054 22.4804 10.2929 22.2929C10.4804 22.1054 10.7348 22 11 22H17C17.2652 22 17.5196 22.1054 17.7071 22.2929C17.8946 22.4804 18 22.7348 18 23Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M27.96 8.26876L16.96 2.25001C16.6661 2.08761 16.3358 2.00243 16 2.00243C15.6642 2.00243 15.3339 2.08761 15.04 2.25001L4.04 8.27126C3.72586 8.44314 3.46363 8.69621 3.28069 9.00404C3.09775 9.31187 3.00081 9.66317 3 10.0213V21.9763C3.00081 22.3343 3.09775 22.6856 3.28069 22.9935C3.46363 23.3013 3.72586 23.5544 4.04 23.7263L15.04 29.7475C15.3339 29.9099 15.6642 29.9951 16 29.9951C16.3358 29.9951 16.6661 29.9099 16.96 29.7475L27.96 23.7263C28.2741 23.5544 28.5364 23.3013 28.7193 22.9935C28.9023 22.6856 28.9992 22.3343 29 21.9763V10.0225C28.9999 9.66378 28.9032 9.3117 28.7203 9.00315C28.5373 8.6946 28.2747 8.44095 27.96 8.26876ZM16 4.00001L26.0425 9.50001L16 15L5.9575 9.50001L16 4.00001ZM5 11.25L15 16.7225V27.4463L5 21.9775V11.25ZM17 27.4463V16.7275L27 11.25V21.9725L17 27.4463Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 911 B

View File

@ -0,0 +1,7 @@
<svg width="48" height="32" version="1.1" viewBox="0 0 48 32" xmlns="http://www.w3.org/2000/svg">
<path d="m6.1766 24v-24h1.433v32h-1.433l-4.2991-24v24h-1.433v-32h1.433z" stroke-width="1.0591"/>
<path d="m14.909 30.609c0.38816 0 0.71659-0.13038 0.98528-0.39123 0.29856-0.26096 0.44779-0.59426 0.44779-1v-26.435c0-0.37682-0.14923-0.69565-0.44779-0.95653-0.26869-0.28985-0.59712-0.43478-0.98528-0.43478-0.41792 0-0.76128 0.14493-1.03 0.43478-0.26869 0.26088-0.40299 0.57971-0.40299 0.95653v26.435c0 0.40574 0.13429 0.73904 0.40299 1 0.26869 0.26086 0.61206 0.39123 1.03 0.39123zm0 1.3913c-0.80608 0-1.4927-0.26086-2.06-0.78268-0.53739-0.55063-0.80608-1.2173-0.80608-1.9999v-26.435c0-0.75362 0.26869-1.4058 0.80608-1.9565 0.56726-0.55072 1.2539-0.82609 2.06-0.82609 0.77632 0 1.448 0.27536 2.0153 0.82609 0.56725 0.55072 0.85088 1.2029 0.85088 1.9565v26.435c0 0.78257-0.28363 1.4493-0.85088 1.9999-0.56726 0.52182-1.2389 0.78268-2.0153 0.78268z" stroke-width="1.0591"/>
<path d="m30.449 0h1.4331v32h-1.4331v-26.435l-3.5826 13.913-3.5826-13.913v26.435h-1.433v-32h1.433l3.5826 13.913z" stroke-width="1.0591"/>
<path d="m36.136 32v-32h1.433v32z" stroke-width="1.0591"/>
<path d="m44.689 32c-0.80608 0-1.4927-0.26086-2.06-0.78268-0.53739-0.55063-0.80608-1.2173-0.80608-1.9999v-26.435c0-0.75362 0.26869-1.4058 0.80608-1.9565 0.56726-0.55072 1.2539-0.82609 2.06-0.82609 0.77622 0 1.448 0.27536 2.0153 0.82609 0.56715 0.55072 0.85078 1.2029 0.85078 1.9565v2.7826h-1.433v-2.7826c0-0.37682-0.14933-0.69565-0.44789-0.95653-0.2687-0.28985-0.59702-0.43478-0.98518-0.43478-0.41792 0-0.76128 0.14493-1.03 0.43478-0.2687 0.26088-0.4031 0.57971-0.4031 0.95653v26.435c0 0.40574 0.1344 0.73904 0.4031 1 0.26869 0.26086 0.61205 0.39123 1.03 0.39123 0.38816 0 0.71648-0.13038 0.98518-0.39123 0.29856-0.26096 0.44789-0.59426 0.44789-1v-2.7827h1.433v2.7827c0 0.78257-0.28363 1.4493-0.85078 1.9999-0.56726 0.52182-1.239 0.78268-2.0153 0.78268z" stroke-width="1.0591"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M88,96a8,8,0,0,1,8-8h64a8,8,0,0,1,0,16H96A8,8,0,0,1,88,96Zm8,40h64a8,8,0,0,0,0-16H96a8,8,0,0,0,0,16Zm32,16H96a8,8,0,0,0,0,16h32a8,8,0,0,0,0-16ZM224,48V156.69A15.86,15.86,0,0,1,219.31,168L168,219.31A15.86,15.86,0,0,1,156.69,224H48a16,16,0,0,1-16-16V48A16,16,0,0,1,48,32H208A16,16,0,0,1,224,48ZM48,208H152V160a8,8,0,0,1,8-8h48V48H48Zm120-40v28.7L196.69,168Z"></path></svg>

After

Width:  |  Height:  |  Size: 479 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"/></svg>
<!--
Font Awesome Free 5.2.0 by @fontawesome - https://fontawesome.com
License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
-->

After

Width:  |  Height:  |  Size: 602 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,5 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#7d7d8e" viewBox="0 0 448 512"><path d="M0 84V56c0-13.3 10.7-24 24-24h112l9.4-18.7c4-8.2 12.3-13.3 21.4-13.3h114.3c9.1 0 17.4 5.1 21.5 13.3L312 32h112c13.3 0 24 10.7 24 24v28c0 6.6-5.4 12-12 12H12C5.4 96 0 90.6 0 84zm416 56v324c0 26.5-21.5 48-48 48H80c-26.5 0-48-21.5-48-48V140c0-6.6 5.4-12 12-12h360c6.6 0 12 5.4 12 12zm-272 68c0-8.8-7.2-16-16-16s-16 7.2-16 16v224c0 8.8 7.2 16 16 16s16-7.2 16-16V208zm96 0c0-8.8-7.2-16-16-16s-16 7.2-16 16v224c0 8.8 7.2 16 16 16s16-7.2 16-16V208zm96 0c0-8.8-7.2-16-16-16s-16 7.2-16 16v224c0 8.8 7.2 16 16 16s16-7.2 16-16V208z"/></svg> <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- <path d="M27 6H5C4.73478 6 4.48043 6.10536 4.29289 6.29289C4.10536 6.48043 4 6.73478 4 7C4 7.26522 4.10536 7.51957 4.29289 7.70711C4.48043 7.89464 4.73478 8 5 8H6V26C6 26.5304 6.21071 27.0391 6.58579 27.4142C6.96086 27.7893 7.46957 28 8 28H24C24.5304 28 25.0391 27.7893 25.4142 27.4142C25.7893 27.0391 26 26.5304 26 26V8H27C27.2652 8 27.5196 7.89464 27.7071 7.70711C27.8946 7.51957 28 7.26522 28 7C28 6.73478 27.8946 6.48043 27.7071 6.29289C27.5196 6.10536 27.2652 6 27 6ZM24 26H8V8H24V26ZM10 3C10 2.73478 10.1054 2.48043 10.2929 2.29289C10.4804 2.10536 10.7348 2 11 2H21C21.2652 2 21.5196 2.10536 21.7071 2.29289C21.8946 2.48043 22 2.73478 22 3C22 3.26522 21.8946 3.51957 21.7071 3.70711C21.5196 3.89464 21.2652 4 21 4H11C10.7348 4 10.4804 3.89464 10.2929 3.70711C10.1054 3.51957 10 3.26522 10 3Z" fill="black"/>
Font Awesome Free 5.2.0 by @fontawesome - https://fontawesome.com </svg>
License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
-->

Before

Width:  |  Height:  |  Size: 791 B

After

Width:  |  Height:  |  Size: 917 B

View File

@ -0,0 +1,3 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M53.687 52.9275L38.037 28.3325L53.4795 11.345C53.8288 10.9513 54.0088 10.4358 53.9805 9.91031C53.9522 9.38479 53.718 8.89159 53.3285 8.53764C52.939 8.18369 52.4257 7.99752 51.8999 8.01949C51.3741 8.04146 50.8781 8.2698 50.5195 8.655L35.8095 24.835L25.687 8.9275C25.5065 8.64339 25.2572 8.40943 24.9622 8.24729C24.6673 8.08516 24.3361 8.0001 23.9995 8H11.9995C11.6409 7.99983 11.2889 8.09607 10.9802 8.27865C10.6716 8.46123 10.4177 8.72343 10.2452 9.0378C10.0727 9.35218 9.98787 9.70715 9.99963 10.0656C10.0114 10.424 10.1193 10.7726 10.312 11.075L25.962 35.6675L10.5195 52.6675C10.3392 52.8612 10.1991 53.0888 10.1074 53.337C10.0156 53.5853 9.97409 53.8493 9.98514 54.1137C9.99619 54.3782 10.0596 54.6378 10.1717 54.8775C10.2839 55.1173 10.4424 55.3324 10.6383 55.5104C10.8342 55.6883 11.0634 55.8257 11.3127 55.9144C11.5621 56.0032 11.8266 56.0416 12.0908 56.0274C12.3551 56.0131 12.6139 55.9466 12.8523 55.8316C13.0907 55.7166 13.3039 55.5555 13.4795 55.3575L28.1895 39.1775L38.312 55.085C38.494 55.3668 38.7439 55.5983 39.0388 55.7582C39.3337 55.9181 39.6641 56.0012 39.9995 56H51.9995C52.3577 55.9999 52.7093 55.9036 53.0176 55.7211C53.3259 55.5387 53.5795 55.2768 53.7519 54.9628C53.9244 54.6488 54.0093 54.2943 53.9979 53.9363C53.9865 53.5782 53.8791 53.2299 53.687 52.9275ZM41.097 52L15.642 12H22.892L48.357 52H41.097Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="64"
height="64"
viewBox="0 0 64 64"
fill="none"
version="1.1"
id="svg621"
sodipodi:docname="you.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs625" />
<sodipodi:namedview
id="namedview623"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="8.3235294"
inkscape:cx="40.787986"
inkscape:cy="14.477032"
inkscape:window-width="2560"
inkscape:window-height="1495"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg621"
width="64px" />
<path
d="m 31.999923,7.9690376 c -4.752845,0 -9.398967,1.4093898 -13.35082,4.0499444 -3.951883,2.640563 -7.031978,6.393677 -8.8508121,10.784758 -1.8188407,4.39108 -2.2947332,9.22288 -1.3674948,13.884408 0.9272374,4.661556 3.2159689,8.943354 6.5767279,12.304134 3.36079,3.360781 7.642685,5.649708 12.304217,6.576935 4.661533,0.927224 9.493333,0.451338 13.884287,-1.36762 4.391329,-1.818661 8.144428,-4.898761 10.784829,-8.850779 C 54.621568,41.3991 56.030962,36.75292 56.030962,31.999972 56.024115,25.628625 53.49019,19.520183 48.984915,15.014967 44.479639,10.509751 38.371319,7.9757662 31.999923,7.9690376 Z M 19.54085,48.059143 c 1.337261,-2.091357 3.179471,-3.81271 5.35686,-5.00494 2.177357,-1.191937 4.619814,-1.816888 7.102213,-1.816888 2.482401,0 4.924858,0.624951 7.10209,1.816888 2.177452,1.19223 4.019599,2.913583 5.357077,5.00494 -3.562251,2.770434 -7.946388,4.274401 -12.459167,4.274401 -4.512778,0 -8.89682,-1.503967 -12.459073,-4.274401 z m 5.064955,-17.907705 c 0,-1.462412 0.433684,-2.891993 1.246135,-4.107973 0.812481,-1.21595 1.967303,-2.163671 3.318387,-2.723318 1.351085,-0.559648 2.837816,-0.706082 4.272119,-0.420757 1.434335,0.285293 2.751827,0.989513 3.785914,2.023598 1.033992,1.034084 1.738224,2.3516 2.023715,3.785914 0.285169,1.434344 0.138863,2.921037 -0.42092,4.272153 -0.559463,1.351086 -1.50731,2.505901 -2.723277,3.318369 -1.215936,0.812555 -2.645538,1.246149 -4.107955,1.246149 -1.961045,0 -3.841766,-0.779046 -5.228435,-2.165684 -1.386639,-1.386666 -2.165683,-3.267408 -2.165683,-5.228451 z M 47.194758,45.49663 c -2.061635,-2.987526 -4.960441,-5.298928 -8.332158,-6.643183 1.811014,-1.426477 3.132617,-3.381959 3.78112,-5.594403 0.648191,-2.212443 0.590906,-4.571881 -0.164073,-6.750252 -0.754668,-2.178399 -2.169669,-4.067394 -4.04793,-5.404342 -1.878199,-1.336919 -4.126323,-2.055363 -6.431794,-2.055363 -2.305439,0 -4.553593,0.718444 -6.431792,2.055363 -1.878231,1.336948 -3.293139,3.225943 -4.047993,5.404342 -0.754853,2.178371 -0.812138,4.537809 -0.163884,6.750252 0.648284,2.212444 1.969887,4.167926 3.781025,5.594403 -3.371687,1.344255 -6.270618,3.655657 -8.332252,6.643183 -2.605128,-2.929553 -4.30783,-6.549722 -4.903126,-10.424632 -0.595297,-3.874852 -0.05775,-7.839172 1.547845,-11.415626 1.605598,-3.576423 4.210849,-6.612484 7.501995,-8.742528 3.291146,-2.130072 7.127899,-3.263356 11.048182,-3.263356 3.920316,0 7.757131,1.133284 11.048216,3.263356 3.291082,2.130044 5.896303,5.166105 7.501836,8.742528 1.605847,3.576454 2.143208,7.540774 1.54794,11.415626 -0.595264,3.87491 -2.297935,7.495079 -4.903157,10.424632 z"
fill="#5e895f"
id="path619"
style="stroke-width:3.03448" />
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -13,12 +13,14 @@
#include <QUrl> #include <QUrl>
#include <QtLogging> #include <QtLogging>
#include <string> #ifdef GPT4ALL_OFFLINE_INSTALLER
# include <QDesktopServices>
#ifndef GPT4ALL_OFFLINE_INSTALLER #else
# include "network.h" # include "network.h"
#endif #endif
using namespace Qt::Literals::StringLiterals;
class MyLLM: public LLM { }; class MyLLM: public LLM { };
Q_GLOBAL_STATIC(MyLLM, llmInstance) Q_GLOBAL_STATIC(MyLLM, llmInstance)
LLM *LLM::globalInstance() LLM *LLM::globalInstance()
@ -54,11 +56,11 @@ bool LLM::checkForUpdates() const
Network::globalInstance()->trackEvent("check_for_updates"); Network::globalInstance()->trackEvent("check_for_updates");
#if defined(Q_OS_LINUX) #if defined(Q_OS_LINUX)
QString tool("maintenancetool"); QString tool = u"maintenancetool"_s;
#elif defined(Q_OS_WINDOWS) #elif defined(Q_OS_WINDOWS)
QString tool("maintenancetool.exe"); QString tool = u"maintenancetool.exe"_s;
#elif defined(Q_OS_DARWIN) #elif defined(Q_OS_DARWIN)
QString tool("../../../maintenancetool.app/Contents/MacOS/maintenancetool"); QString tool = u"../../../maintenancetool.app/Contents/MacOS/maintenancetool"_s;
#endif #endif
QString fileName = QCoreApplication::applicationDirPath() QString fileName = QCoreApplication::applicationDirPath()

View File

@ -1,6 +1,7 @@
#include "localdocs.h" #include "localdocs.h"
#include "database.h" #include "database.h"
#include "embllm.h"
#include "mysettings.h" #include "mysettings.h"
#include <QCoreApplication> #include <QCoreApplication>
@ -22,45 +23,37 @@ LocalDocs::LocalDocs()
, m_database(nullptr) , m_database(nullptr)
{ {
connect(MySettings::globalInstance(), &MySettings::localDocsChunkSizeChanged, this, &LocalDocs::handleChunkSizeChanged); connect(MySettings::globalInstance(), &MySettings::localDocsChunkSizeChanged, this, &LocalDocs::handleChunkSizeChanged);
connect(MySettings::globalInstance(), &MySettings::localDocsFileExtensionsChanged, this, &LocalDocs::handleFileExtensionsChanged);
// 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(),
MySettings::globalInstance()->localDocsFileExtensions());
connect(this, &LocalDocs::requestStart, m_database, connect(this, &LocalDocs::requestStart, m_database,
&Database::start, Qt::QueuedConnection); &Database::start, Qt::QueuedConnection);
connect(this, &LocalDocs::requestForceIndexing, m_database,
&Database::forceIndexing, Qt::QueuedConnection);
connect(this, &LocalDocs::forceRebuildFolder, m_database,
&Database::forceRebuildFolder, 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,
&Database::removeFolder, Qt::QueuedConnection); &Database::removeFolder, Qt::QueuedConnection);
connect(this, &LocalDocs::requestChunkSizeChange, m_database, connect(this, &LocalDocs::requestChunkSizeChange, m_database,
&Database::changeChunkSize, Qt::QueuedConnection); &Database::changeChunkSize, Qt::QueuedConnection);
connect(this, &LocalDocs::requestFileExtensionsChange, m_database,
&Database::changeFileExtensions, Qt::QueuedConnection);
connect(m_database, &Database::databaseValidChanged,
this, &LocalDocs::databaseValidChanged, Qt::QueuedConnection);
// Connections for modifying the model and keeping it updated with the database // Connections for modifying the model and keeping it updated with the database
connect(m_database, &Database::updateInstalled, connect(m_database, &Database::requestUpdateGuiForCollectionItem,
m_localDocsModel, &LocalDocsModel::updateInstalled, Qt::QueuedConnection); m_localDocsModel, &LocalDocsModel::updateCollectionItem, Qt::QueuedConnection);
connect(m_database, &Database::updateIndexing, connect(m_database, &Database::requestAddGuiCollectionItem,
m_localDocsModel, &LocalDocsModel::updateIndexing, Qt::QueuedConnection);
connect(m_database, &Database::updateError,
m_localDocsModel, &LocalDocsModel::updateError, Qt::QueuedConnection);
connect(m_database, &Database::updateCurrentDocsToIndex,
m_localDocsModel, &LocalDocsModel::updateCurrentDocsToIndex, Qt::QueuedConnection);
connect(m_database, &Database::updateTotalDocsToIndex,
m_localDocsModel, &LocalDocsModel::updateTotalDocsToIndex, Qt::QueuedConnection);
connect(m_database, &Database::subtractCurrentBytesToIndex,
m_localDocsModel, &LocalDocsModel::subtractCurrentBytesToIndex, Qt::QueuedConnection);
connect(m_database, &Database::updateCurrentBytesToIndex,
m_localDocsModel, &LocalDocsModel::updateCurrentBytesToIndex, Qt::QueuedConnection);
connect(m_database, &Database::updateTotalBytesToIndex,
m_localDocsModel, &LocalDocsModel::updateTotalBytesToIndex, Qt::QueuedConnection);
connect(m_database, &Database::updateCurrentEmbeddingsToIndex,
m_localDocsModel, &LocalDocsModel::updateCurrentEmbeddingsToIndex, Qt::QueuedConnection);
connect(m_database, &Database::updateTotalEmbeddingsToIndex,
m_localDocsModel, &LocalDocsModel::updateTotalEmbeddingsToIndex, Qt::QueuedConnection);
connect(m_database, &Database::addCollectionItem,
m_localDocsModel, &LocalDocsModel::addCollectionItem, Qt::QueuedConnection); m_localDocsModel, &LocalDocsModel::addCollectionItem, Qt::QueuedConnection);
connect(m_database, &Database::removeFolderById, connect(m_database, &Database::requestRemoveGuiFolderById,
m_localDocsModel, &LocalDocsModel::removeFolderById, Qt::QueuedConnection); m_localDocsModel, &LocalDocsModel::removeFolderById, Qt::QueuedConnection);
connect(m_database, &Database::collectionListUpdated, connect(m_database, &Database::requestGuiCollectionListUpdated,
m_localDocsModel, &LocalDocsModel::collectionListUpdated, Qt::QueuedConnection); m_localDocsModel, &LocalDocsModel::collectionListUpdated, Qt::QueuedConnection);
connect(qGuiApp, &QCoreApplication::aboutToQuit, this, &LocalDocs::aboutToQuit); connect(qGuiApp, &QCoreApplication::aboutToQuit, this, &LocalDocs::aboutToQuit);
@ -76,16 +69,38 @@ 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, false);
const QString embedding_model = EmbeddingLLM::model();
if (embedding_model.isEmpty()) {
qWarning() << "ERROR: We have no embedding model";
return;
}
emit requestAddFolder(collection, localPath, embedding_model);
} }
void LocalDocs::removeFolder(const QString &collection, const QString &path) void LocalDocs::removeFolder(const QString &collection, const QString &path)
{ {
m_localDocsModel->removeCollectionPath(collection, path);
emit requestRemoveFolder(collection, path); emit requestRemoveFolder(collection, path);
} }
void LocalDocs::forceIndexing(const QString &collection)
{
const QString embedding_model = EmbeddingLLM::model();
if (embedding_model.isEmpty()) {
qWarning() << "ERROR: We have no embedding model";
return;
}
emit requestForceIndexing(collection, embedding_model);
}
void LocalDocs::handleChunkSizeChanged() void LocalDocs::handleChunkSizeChanged()
{ {
emit requestChunkSizeChange(MySettings::globalInstance()->localDocsChunkSize()); emit requestChunkSizeChange(MySettings::globalInstance()->localDocsChunkSize());
} }
void LocalDocs::handleFileExtensionsChanged()
{
emit requestFileExtensionsChange(MySettings::globalInstance()->localDocsFileExtensions());
}

View File

@ -1,16 +1,17 @@
#ifndef LOCALDOCS_H #ifndef LOCALDOCS_H
#define LOCALDOCS_H #define LOCALDOCS_H
#include "database.h"
#include "localdocsmodel.h" // IWYU pragma: keep #include "localdocsmodel.h" // IWYU pragma: keep
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QStringList>
class Database;
class LocalDocs : public QObject class LocalDocs : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool databaseValid READ databaseValid NOTIFY databaseValidChanged)
Q_PROPERTY(LocalDocsModel *localDocsModel READ localDocsModel NOTIFY localDocsModelChanged) Q_PROPERTY(LocalDocsModel *localDocsModel READ localDocsModel NOTIFY localDocsModelChanged)
public: public:
@ -20,19 +21,27 @@ public:
Q_INVOKABLE void addFolder(const QString &collection, const QString &path); Q_INVOKABLE void addFolder(const QString &collection, const QString &path);
Q_INVOKABLE void removeFolder(const QString &collection, const QString &path); Q_INVOKABLE void removeFolder(const QString &collection, const QString &path);
Q_INVOKABLE void forceIndexing(const QString &collection);
Database *database() const { return m_database; } Database *database() const { return m_database; }
bool databaseValid() const { return m_database->isValid(); }
public Q_SLOTS: public Q_SLOTS:
void handleChunkSizeChanged(); void handleChunkSizeChanged();
void handleFileExtensionsChanged();
void aboutToQuit(); void aboutToQuit();
Q_SIGNALS: Q_SIGNALS:
void requestStart(); void requestStart();
void requestAddFolder(const QString &collection, const QString &path, bool fromDb); void requestForceIndexing(const QString &collection, const QString &embedding_model);
void forceRebuildFolder(const QString &path);
void requestAddFolder(const QString &collection, const QString &path, const QString &embedding_model);
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 requestFileExtensionsChange(const QStringList &extensions);
void localDocsModelChanged(); void localDocsModelChanged();
void databaseValidChanged();
private: private:
LocalDocsModel *m_localDocsModel; LocalDocsModel *m_localDocsModel;

View File

@ -3,7 +3,9 @@
#include "localdocs.h" #include "localdocs.h"
#include "network.h" #include "network.h"
#include <QDateTime>
#include <QMap> #include <QMap>
#include <QVector>
#include <QtGlobal> #include <QtGlobal>
#include <utility> #include <utility>
@ -12,6 +14,13 @@ LocalDocsCollectionsModel::LocalDocsCollectionsModel(QObject *parent)
: QSortFilterProxyModel(parent) : QSortFilterProxyModel(parent)
{ {
setSourceModel(LocalDocs::globalInstance()->localDocsModel()); setSourceModel(LocalDocs::globalInstance()->localDocsModel());
connect(LocalDocs::globalInstance()->localDocsModel(),
&LocalDocsModel::updatingChanged, this, &LocalDocsCollectionsModel::maybeTriggerUpdatingCountChanged);
connect(this, &LocalDocsCollectionsModel::rowsInserted, this, &LocalDocsCollectionsModel::countChanged);
connect(this, &LocalDocsCollectionsModel::rowsRemoved, this, &LocalDocsCollectionsModel::countChanged);
connect(this, &LocalDocsCollectionsModel::modelReset, this, &LocalDocsCollectionsModel::countChanged);
connect(this, &LocalDocsCollectionsModel::layoutChanged, this, &LocalDocsCollectionsModel::countChanged);
} }
bool LocalDocsCollectionsModel::filterAcceptsRow(int sourceRow, bool LocalDocsCollectionsModel::filterAcceptsRow(int sourceRow,
@ -26,11 +35,39 @@ void LocalDocsCollectionsModel::setCollections(const QList<QString> &collections
{ {
m_collections = collections; m_collections = collections;
invalidateFilter(); invalidateFilter();
maybeTriggerUpdatingCountChanged();
}
int LocalDocsCollectionsModel::updatingCount() const
{
return m_updatingCount;
}
void LocalDocsCollectionsModel::maybeTriggerUpdatingCountChanged()
{
int updatingCount = 0;
for (int row = 0; row < sourceModel()->rowCount(); ++row) {
QModelIndex index = sourceModel()->index(row, 0);
const QString collection = sourceModel()->data(index, LocalDocsModel::CollectionRole).toString();
if (!m_collections.contains(collection))
continue;
bool updating = sourceModel()->data(index, LocalDocsModel::UpdatingRole).toBool();
if (updating)
++updatingCount;
}
if (updatingCount != m_updatingCount) {
m_updatingCount = updatingCount;
emit updatingCountChanged();
}
} }
LocalDocsModel::LocalDocsModel(QObject *parent) LocalDocsModel::LocalDocsModel(QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
{ {
connect(this, &LocalDocsModel::rowsInserted, this, &LocalDocsModel::countChanged);
connect(this, &LocalDocsModel::rowsRemoved, this, &LocalDocsModel::countChanged);
connect(this, &LocalDocsModel::modelReset, this, &LocalDocsModel::countChanged);
connect(this, &LocalDocsModel::layoutChanged, this, &LocalDocsModel::countChanged);
} }
int LocalDocsModel::rowCount(const QModelIndex &parent) const int LocalDocsModel::rowCount(const QModelIndex &parent) const
@ -56,6 +93,8 @@ QVariant LocalDocsModel::data(const QModelIndex &index, int role) const
return item.indexing; return item.indexing;
case ErrorRole: case ErrorRole:
return item.error; return item.error;
case ForceIndexingRole:
return item.forceIndexing;
case CurrentDocsToIndexRole: case CurrentDocsToIndexRole:
return item.currentDocsToIndex; return item.currentDocsToIndex;
case TotalDocsToIndexRole: case TotalDocsToIndexRole:
@ -68,6 +107,22 @@ QVariant LocalDocsModel::data(const QModelIndex &index, int role) const
return quint64(item.currentEmbeddingsToIndex); return quint64(item.currentEmbeddingsToIndex);
case TotalEmbeddingsToIndexRole: case TotalEmbeddingsToIndexRole:
return quint64(item.totalEmbeddingsToIndex); return quint64(item.totalEmbeddingsToIndex);
case TotalDocsRole:
return quint64(item.totalDocs);
case TotalWordsRole:
return quint64(item.totalWords);
case TotalTokensRole:
return quint64(item.totalTokens);
case StartUpdateRole:
return item.startUpdate;
case LastUpdateRole:
return item.lastUpdate;
case FileCurrentlyProcessingRole:
return item.fileCurrentlyProcessing;
case EmbeddingModelRole:
return item.embeddingModel;
case UpdatingRole:
return item.indexing || item.currentEmbeddingsToIndex != 0;
} }
return QVariant(); return QVariant();
@ -81,103 +136,94 @@ QHash<int, QByteArray> LocalDocsModel::roleNames() const
roles[InstalledRole] = "installed"; roles[InstalledRole] = "installed";
roles[IndexingRole] = "indexing"; roles[IndexingRole] = "indexing";
roles[ErrorRole] = "error"; roles[ErrorRole] = "error";
roles[ForceIndexingRole] = "forceIndexing";
roles[CurrentDocsToIndexRole] = "currentDocsToIndex"; roles[CurrentDocsToIndexRole] = "currentDocsToIndex";
roles[TotalDocsToIndexRole] = "totalDocsToIndex"; roles[TotalDocsToIndexRole] = "totalDocsToIndex";
roles[CurrentBytesToIndexRole] = "currentBytesToIndex"; roles[CurrentBytesToIndexRole] = "currentBytesToIndex";
roles[TotalBytesToIndexRole] = "totalBytesToIndex"; roles[TotalBytesToIndexRole] = "totalBytesToIndex";
roles[CurrentEmbeddingsToIndexRole] = "currentEmbeddingsToIndex"; roles[CurrentEmbeddingsToIndexRole] = "currentEmbeddingsToIndex";
roles[TotalEmbeddingsToIndexRole] = "totalEmbeddingsToIndex"; roles[TotalEmbeddingsToIndexRole] = "totalEmbeddingsToIndex";
roles[TotalDocsRole] = "totalDocs";
roles[TotalWordsRole] = "totalWords";
roles[TotalTokensRole] = "totalTokens";
roles[StartUpdateRole] = "startUpdate";
roles[LastUpdateRole] = "lastUpdate";
roles[FileCurrentlyProcessingRole] = "fileCurrentlyProcessing";
roles[EmbeddingModelRole] = "embeddingModel";
roles[UpdatingRole] = "updating";
return roles; return roles;
} }
template<typename T> void LocalDocsModel::updateCollectionItem(const CollectionItem &item)
void LocalDocsModel::updateField(int folder_id, T value,
const std::function<void(CollectionItem&, T)>& updater,
const QVector<int>& roles)
{ {
for (int i = 0; i < m_collectionList.size(); ++i) { for (int i = 0; i < m_collectionList.size(); ++i) {
if (m_collectionList.at(i).folder_id != folder_id) CollectionItem &stored = m_collectionList[i];
if (stored.folder_id != item.folder_id)
continue; continue;
updater(m_collectionList[i], value); QVector<int> changed;
emit dataChanged(this->index(i), this->index(i), roles); if (stored.folder_path != item.folder_path)
changed.append(FolderPathRole);
if (stored.installed != item.installed)
changed.append(InstalledRole);
if (stored.indexing != item.indexing) {
changed.append(IndexingRole);
changed.append(UpdatingRole);
}
if (stored.error != item.error)
changed.append(ErrorRole);
if (stored.forceIndexing != item.forceIndexing)
changed.append(ForceIndexingRole);
if (stored.currentDocsToIndex != item.currentDocsToIndex)
changed.append(CurrentDocsToIndexRole);
if (stored.totalDocsToIndex != item.totalDocsToIndex)
changed.append(TotalDocsToIndexRole);
if (stored.currentBytesToIndex != item.currentBytesToIndex)
changed.append(CurrentBytesToIndexRole);
if (stored.totalBytesToIndex != item.totalBytesToIndex)
changed.append(TotalBytesToIndexRole);
if (stored.currentEmbeddingsToIndex != item.currentEmbeddingsToIndex) {
changed.append(CurrentEmbeddingsToIndexRole);
changed.append(UpdatingRole);
}
if (stored.totalEmbeddingsToIndex != item.totalEmbeddingsToIndex)
changed.append(TotalEmbeddingsToIndexRole);
if (stored.totalDocs != item.totalDocs)
changed.append(TotalDocsRole);
if (stored.totalWords != item.totalWords)
changed.append(TotalWordsRole);
if (stored.totalTokens != item.totalTokens)
changed.append(TotalTokensRole);
if (stored.startUpdate != item.startUpdate)
changed.append(StartUpdateRole);
if (stored.lastUpdate != item.lastUpdate)
changed.append(LastUpdateRole);
if (stored.fileCurrentlyProcessing != item.fileCurrentlyProcessing)
changed.append(FileCurrentlyProcessingRole);
if (stored.embeddingModel != item.embeddingModel)
changed.append(EmbeddingModelRole);
// preserve collection name as we ignore it for matching
QString collection = stored.collection;
stored = item;
stored.collection = collection;
emit dataChanged(this->index(i), this->index(i), changed);
if (changed.contains(UpdatingRole))
emit updatingChanged(item.collection);
} }
} }
void LocalDocsModel::updateInstalled(int folder_id, bool b) void LocalDocsModel::addCollectionItem(const CollectionItem &item)
{
updateField<bool>(folder_id, b,
[](CollectionItem& item, bool val) { item.installed = val; }, {InstalledRole});
}
void LocalDocsModel::updateIndexing(int folder_id, bool b)
{
updateField<bool>(folder_id, b,
[](CollectionItem& item, bool val) { item.indexing = val; }, {IndexingRole});
}
void LocalDocsModel::updateError(int folder_id, const QString &error)
{
updateField<QString>(folder_id, error,
[](CollectionItem& item, QString val) { item.error = val; }, {ErrorRole});
}
void LocalDocsModel::updateCurrentDocsToIndex(int folder_id, size_t currentDocsToIndex)
{
updateField<size_t>(folder_id, currentDocsToIndex,
[](CollectionItem& item, size_t val) { item.currentDocsToIndex = val; }, {CurrentDocsToIndexRole});
}
void LocalDocsModel::updateTotalDocsToIndex(int folder_id, size_t totalDocsToIndex)
{
updateField<size_t>(folder_id, totalDocsToIndex,
[](CollectionItem& item, size_t val) { item.totalDocsToIndex = val; }, {TotalDocsToIndexRole});
}
void LocalDocsModel::subtractCurrentBytesToIndex(int folder_id, size_t subtractedBytes)
{
updateField<size_t>(folder_id, subtractedBytes,
[](CollectionItem& item, size_t val) { item.currentBytesToIndex -= val; }, {CurrentBytesToIndexRole});
}
void LocalDocsModel::updateCurrentBytesToIndex(int folder_id, size_t currentBytesToIndex)
{
updateField<size_t>(folder_id, currentBytesToIndex,
[](CollectionItem& item, size_t val) { item.currentBytesToIndex = val; }, {CurrentBytesToIndexRole});
}
void LocalDocsModel::updateTotalBytesToIndex(int folder_id, size_t totalBytesToIndex)
{
updateField<size_t>(folder_id, totalBytesToIndex,
[](CollectionItem& item, size_t val) { item.totalBytesToIndex = val; }, {TotalBytesToIndexRole});
}
void LocalDocsModel::updateCurrentEmbeddingsToIndex(int folder_id, size_t currentEmbeddingsToIndex)
{
updateField<size_t>(folder_id, currentEmbeddingsToIndex,
[](CollectionItem& item, size_t val) { item.currentEmbeddingsToIndex += val; }, {CurrentEmbeddingsToIndexRole});
}
void LocalDocsModel::updateTotalEmbeddingsToIndex(int folder_id, size_t totalEmbeddingsToIndex)
{
updateField<size_t>(folder_id, totalEmbeddingsToIndex,
[](CollectionItem& item, size_t val) { item.totalEmbeddingsToIndex += val; }, {TotalEmbeddingsToIndexRole});
}
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) { void LocalDocsModel::removeCollectionIf(std::function<bool(CollectionItem)> const &predicate)
{
for (int i = 0; i < m_collectionList.size();) { for (int i = 0; i < m_collectionList.size();) {
if (predicate(m_collectionList.at(i))) { if (predicate(m_collectionList.at(i))) {
beginRemoveRows(QModelIndex(), i, i); beginRemoveRows(QModelIndex(), i, i);
@ -193,9 +239,11 @@ void LocalDocsModel::removeCollectionIf(std::function<bool(CollectionItem)> cons
} }
} }
void LocalDocsModel::removeFolderById(int folder_id) void LocalDocsModel::removeFolderById(const QString &collection, int folder_id)
{ {
removeCollectionIf([folder_id](const auto &c) { return c.folder_id == folder_id; }); removeCollectionIf([collection, folder_id](const auto &c) {
return c.collection == collection && c.folder_id == folder_id;
});
} }
void LocalDocsModel::removeCollectionPath(const QString &name, const QString &path) void LocalDocsModel::removeCollectionPath(const QString &name, const QString &path)

View File

@ -7,36 +7,46 @@
#include <QByteArray> #include <QByteArray>
#include <QHash> #include <QHash>
#include <QList> #include <QList>
#include <QModelIndex>
#include <QObject> #include <QObject>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QString> #include <QString>
#include <QVariant> #include <QVariant>
#include <QVector>
#include <Qt> #include <Qt>
#include <cstddef>
#include <functional> #include <functional>
class LocalDocsCollectionsModel : public QSortFilterProxyModel class LocalDocsCollectionsModel : public QSortFilterProxyModel
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(int updatingCount READ updatingCount NOTIFY updatingCountChanged)
public: public:
explicit LocalDocsCollectionsModel(QObject *parent); explicit LocalDocsCollectionsModel(QObject *parent);
public Q_SLOTS: public Q_SLOTS:
int count() const { return rowCount(); }
void setCollections(const QList<QString> &collections); void setCollections(const QList<QString> &collections);
int updatingCount() const;
Q_SIGNALS:
void countChanged();
void updatingCountChanged();
private Q_SLOT:
void maybeTriggerUpdatingCountChanged();
protected: protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
private: private:
QList<QString> m_collections; QList<QString> m_collections;
int m_updatingCount = 0;
}; };
class LocalDocsModel : public QAbstractListModel class LocalDocsModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public: public:
enum Roles { enum Roles {
@ -45,43 +55,42 @@ public:
InstalledRole, InstalledRole,
IndexingRole, IndexingRole,
ErrorRole, ErrorRole,
ForceIndexingRole,
CurrentDocsToIndexRole, CurrentDocsToIndexRole,
TotalDocsToIndexRole, TotalDocsToIndexRole,
CurrentBytesToIndexRole, CurrentBytesToIndexRole,
TotalBytesToIndexRole, TotalBytesToIndexRole,
CurrentEmbeddingsToIndexRole, CurrentEmbeddingsToIndexRole,
TotalEmbeddingsToIndexRole TotalEmbeddingsToIndexRole,
TotalDocsRole,
TotalWordsRole,
TotalTokensRole,
StartUpdateRole,
LastUpdateRole,
FileCurrentlyProcessingRole,
EmbeddingModelRole,
UpdatingRole
}; };
explicit LocalDocsModel(QObject *parent = nullptr); explicit LocalDocsModel(QObject *parent = nullptr);
int rowCount(const QModelIndex & = QModelIndex()) const override; int rowCount(const QModelIndex & = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
int count() const { return rowCount(); }
public Q_SLOTS: public Q_SLOTS:
void updateInstalled(int folder_id, bool b); void updateCollectionItem(const CollectionItem&);
void updateIndexing(int folder_id, bool b); void addCollectionItem(const CollectionItem &item);
void updateError(int folder_id, const QString &error); void removeFolderById(const QString &collection, int folder_id);
void updateCurrentDocsToIndex(int folder_id, size_t currentDocsToIndex);
void updateTotalDocsToIndex(int folder_id, size_t totalDocsToIndex);
void subtractCurrentBytesToIndex(int folder_id, size_t subtractedBytes);
void updateCurrentBytesToIndex(int folder_id, size_t currentBytesToIndex);
void updateTotalBytesToIndex(int folder_id, size_t totalBytesToIndex);
void updateCurrentEmbeddingsToIndex(int folder_id, size_t currentBytesToIndex);
void updateTotalEmbeddingsToIndex(int folder_id, size_t totalBytesToIndex);
void addCollectionItem(const CollectionItem &item, bool fromDb);
void removeFolderById(int folder_id);
void removeCollectionPath(const QString &name, const QString &path); void removeCollectionPath(const QString &name, const QString &path);
void collectionListUpdated(const QList<CollectionItem> &collectionList); void collectionListUpdated(const QList<CollectionItem> &collectionList);
private: Q_SIGNALS:
template<typename T> void countChanged();
void updateField(int folder_id, T value, void updatingChanged(const QString &collection);
const std::function<void(CollectionItem&, T)>& updater,
const QVector<int>& roles);
void removeCollectionIf(std::function<bool(CollectionItem)> const &predicate);
private: private:
void removeCollectionIf(std::function<bool(CollectionItem)> const &predicate);
QList<CollectionItem> m_collectionList; QList<CollectionItem> m_collectionList;
}; };

View File

@ -10,6 +10,8 @@
#include <iostream> #include <iostream>
#include <string> #include <string>
using namespace Qt::Literals::StringLiterals;
class MyLogger: public Logger { }; class MyLogger: public Logger { };
Q_GLOBAL_STATIC(MyLogger, loggerInstance) Q_GLOBAL_STATIC(MyLogger, loggerInstance)
Logger *Logger::globalInstance() Logger *Logger::globalInstance()
@ -61,7 +63,7 @@ void Logger::messageHandler(QtMsgType type, const QMessageLogContext &, const QS
// Get time and date // Get time and date
auto timestamp = QDateTime::currentDateTime().toString(); auto timestamp = QDateTime::currentDateTime().toString();
// Write message // Write message
const std::string out = QString("[%1] (%2): %4\n").arg(typeString, timestamp, msg).toStdString(); const std::string out = u"[%1] (%2): %3\n"_s.arg(typeString, timestamp, msg).toStdString();
logger->m_file.write(out.c_str()); logger->m_file.write(out.c_str());
logger->m_file.flush(); logger->m_file.flush();
std::cerr << out; std::cerr << out;

View File

@ -15,9 +15,10 @@
#include <QObject> #include <QObject>
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QQmlEngine> #include <QQmlEngine>
#include <QSettings>
#include <QString> #include <QString>
#include <Qt>
#include <QUrl> #include <QUrl>
#include <Qt>
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
@ -25,6 +26,7 @@ int main(int argc, char *argv[])
QCoreApplication::setOrganizationDomain("gpt4all.io"); QCoreApplication::setOrganizationDomain("gpt4all.io");
QCoreApplication::setApplicationName("GPT4All"); QCoreApplication::setApplicationName("GPT4All");
QCoreApplication::setApplicationVersion(APP_VERSION); QCoreApplication::setApplicationVersion(APP_VERSION);
QSettings::setDefaultFormat(QSettings::IniFormat);
Logger::globalInstance(); Logger::globalInstance();

View File

@ -10,14 +10,15 @@ import download
import modellist import modellist
import network import network
import gpt4all import gpt4all
import localdocs
import mysettings import mysettings
Window { Window {
id: window id: window
width: 1920 width: 1920
height: 1080 height: 1080
minimumWidth: 720 minimumWidth: 1280
minimumHeight: 480 minimumHeight: 720
visible: true visible: true
title: qsTr("GPT4All v") + Qt.application.version title: qsTr("GPT4All v") + Qt.application.version
@ -32,6 +33,128 @@ Window {
id: theme id: theme
} }
Item {
Accessible.role: Accessible.Window
Accessible.name: title
}
// Startup code
Component.onCompleted: {
startupDialogs();
}
Component.onDestruction: {
Network.trackEvent("session_end")
}
Connections {
target: firstStartDialog
function onClosed() {
startupDialogs();
}
}
Connections {
target: Download
function onHasNewerReleaseChanged() {
startupDialogs();
}
}
property bool hasCheckedFirstStart: false
property bool hasShownSettingsAccess: false
function startupDialogs() {
if (!LLM.compatHardware()) {
Network.trackEvent("noncompat_hardware")
errorCompatHardware.open();
return;
}
// check if we have access to settings and if not show an error
if (!hasShownSettingsAccess && !LLM.hasSettingsAccess()) {
errorSettingsAccess.open();
hasShownSettingsAccess = true;
return;
}
// check for first time start of this version
if (!hasCheckedFirstStart) {
if (Download.isFirstStart(/*writeVersion*/ true)) {
firstStartDialog.open();
return;
}
// send startup or opt-out now that the user has made their choice
Network.sendStartup()
// start localdocs
LocalDocs.requestStart()
hasCheckedFirstStart = true
}
// check for new version
if (Download.hasNewerRelease && !firstStartDialog.opened) {
newVersionDialog.open();
return;
}
}
PopupDialog {
id: errorCompatHardware
anchors.centerIn: parent
shouldTimeOut: false
shouldShowBusy: false
closePolicy: Popup.NoAutoClose
modal: true
text: qsTr("<h3>Encountered an error starting up:</h3><br>")
+ qsTr("<i>\"Incompatible hardware detected.\"</i>")
+ qsTr("<br><br>Unfortunately, your CPU does not meet the minimal requirements to run ")
+ qsTr("this program. In particular, it does not support AVX intrinsics which this ")
+ qsTr("program requires to successfully run a modern large language model. ")
+ qsTr("The only solution at this time is to upgrade your hardware to a more modern CPU.")
+ qsTr("<br><br>See here for more information: <a href=\"https://en.wikipedia.org/wiki/Advanced_Vector_Extensions\">")
+ qsTr("https://en.wikipedia.org/wiki/Advanced_Vector_Extensions</a>")
}
PopupDialog {
id: errorSettingsAccess
anchors.centerIn: parent
shouldTimeOut: false
shouldShowBusy: false
modal: true
text: qsTr("<h3>Encountered an error starting up:</h3><br>")
+ qsTr("<i>\"Inability to access settings file.\"</i>")
+ qsTr("<br><br>Unfortunately, something is preventing the program from accessing ")
+ qsTr("the settings file. This could be caused by incorrect permissions in the local ")
+ qsTr("app config directory where the settings file is located. ")
+ qsTr("Check out our <a href=\"https://discord.gg/4M2QFmTt2k\">discord channel</a> for help.")
}
StartupDialog {
id: firstStartDialog
anchors.centerIn: parent
}
NewVersionDialog {
id: newVersionDialog
anchors.centerIn: parent
}
Connections {
target: Network
function onHealthCheckFailed(code) {
healthCheckFailed.open();
}
}
PopupDialog {
id: healthCheckFailed
anchors.centerIn: parent
text: qsTr("Connection to datalake failed.")
font.pixelSize: theme.fontSizeLarge
}
property bool hasSaved: false property bool hasSaved: false
PopupDialog { PopupDialog {
@ -43,6 +166,18 @@ Window {
font.pixelSize: theme.fontSizeLarge font.pixelSize: theme.fontSizeLarge
} }
NetworkDialog {
id: networkDialog
anchors.centerIn: parent
width: Math.min(1024, window.width - (window.width * .2))
height: Math.min(600, window.height - (window.height * .2))
Item {
Accessible.role: Accessible.Dialog
Accessible.name: qsTr("Network dialog")
Accessible.description: qsTr("opt-in to share feedback/conversations")
}
}
onClosing: function(close) { onClosing: function(close) {
if (window.hasSaved) if (window.hasSaved)
return; return;
@ -61,9 +196,440 @@ Window {
} }
} }
color: theme.black color: theme.viewBarBackground
ChatView { Rectangle {
anchors.fill: parent id: viewBar
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
width: MySettings.fontSize === "Small" ? 86 : 100
color: theme.viewBarBackground
ColumnLayout {
id: viewsLayout
anchors.top: parent.top
anchors.topMargin: 30
anchors.horizontalCenter: parent.horizontalCenter
Layout.margins: 0
spacing: 18
MyToolButton {
id: homeButton
backgroundColor: toggled ? theme.iconBackgroundViewBarHovered : theme.iconBackgroundViewBar
backgroundColorHovered: theme.iconBackgroundViewBarHovered
Layout.preferredWidth: 48
Layout.preferredHeight: 48
Layout.alignment: Qt.AlignCenter
toggledWidth: 0
toggled: homeView.isShown()
toggledColor: theme.iconBackgroundViewBarToggled
imageWidth: 34
imageHeight: 34
source: "qrc:/gpt4all/icons/home.svg"
Accessible.name: qsTr("Home view")
Accessible.description: qsTr("Home view of application")
onClicked: {
homeView.show()
}
}
Text {
Layout.topMargin: -20
text: qsTr("Home")
font.pixelSize: theme.fontSizeLargeCapped
font.bold: true
color: homeButton.hovered ? homeButton.backgroundColorHovered : homeButton.backgroundColor
Layout.preferredWidth: 48
horizontalAlignment: Text.AlignHCenter
TapHandler {
onTapped: function(eventPoint, button) {
homeView.show()
}
}
}
MyToolButton {
id: chatButton
backgroundColor: toggled ? theme.iconBackgroundViewBarHovered : theme.iconBackgroundViewBar
backgroundColorHovered: theme.iconBackgroundViewBarHovered
Layout.preferredWidth: 48
Layout.preferredHeight: 48
Layout.alignment: Qt.AlignCenter
toggledWidth: 0
toggled: chatView.isShown()
toggledColor: theme.iconBackgroundViewBarToggled
imageWidth: 34
imageHeight: 34
source: "qrc:/gpt4all/icons/chat.svg"
Accessible.name: qsTr("Chat view")
Accessible.description: qsTr("Chat view to interact with models")
onClicked: {
chatView.show()
}
}
Text {
Layout.topMargin: -20
text: qsTr("Chats")
font.pixelSize: theme.fontSizeLargeCapped
font.bold: true
color: chatButton.hovered ? chatButton.backgroundColorHovered : chatButton.backgroundColor
Layout.preferredWidth: 48
horizontalAlignment: Text.AlignHCenter
TapHandler {
onTapped: function(eventPoint, button) {
chatView.show()
}
}
}
MyToolButton {
id: modelsButton
backgroundColor: toggled ? theme.iconBackgroundViewBarHovered : theme.iconBackgroundViewBar
backgroundColorHovered: theme.iconBackgroundViewBarHovered
Layout.preferredWidth: 48
Layout.preferredHeight: 48
toggledWidth: 0
toggled: modelsView.isShown()
toggledColor: theme.iconBackgroundViewBarToggled
imageWidth: 34
imageHeight: 34
source: "qrc:/gpt4all/icons/models.svg"
Accessible.name: qsTr("Models")
Accessible.description: qsTr("Models view for installed models")
onClicked: {
modelsView.show()
}
}
Text {
Layout.topMargin: -20
text: qsTr("Models")
font.pixelSize: theme.fontSizeLargeCapped
font.bold: true
color: modelsButton.hovered ? modelsButton.backgroundColorHovered : modelsButton.backgroundColor
Layout.preferredWidth: 48
horizontalAlignment: Text.AlignHCenter
TapHandler {
onTapped: function(eventPoint, button) {
modelsView.show()
}
}
}
MyToolButton {
id: localdocsButton
backgroundColor: toggled ? theme.iconBackgroundViewBarHovered : theme.iconBackgroundViewBar
backgroundColorHovered: theme.iconBackgroundViewBarHovered
Layout.preferredWidth: 48
Layout.preferredHeight: 48
toggledWidth: 0
toggledColor: theme.iconBackgroundViewBarToggled
toggled: localDocsView.isShown()
imageWidth: 34
imageHeight: 34
source: "qrc:/gpt4all/icons/db.svg"
Accessible.name: qsTr("LocalDocs")
Accessible.description: qsTr("LocalDocs view to configure and use local docs")
onClicked: {
localDocsView.show()
}
}
Text {
Layout.topMargin: -20
text: qsTr("LocalDocs")
font.pixelSize: theme.fontSizeLargeCapped
font.bold: true
color: localdocsButton.hovered ? localdocsButton.backgroundColorHovered : localdocsButton.backgroundColor
Layout.preferredWidth: 48
horizontalAlignment: Text.AlignHCenter
TapHandler {
onTapped: function(eventPoint, button) {
localDocsView.show()
}
}
}
MyToolButton {
id: settingsButton
backgroundColor: toggled ? theme.iconBackgroundViewBarHovered : theme.iconBackgroundViewBar
backgroundColorHovered: theme.iconBackgroundViewBarHovered
Layout.preferredWidth: 48
Layout.preferredHeight: 48
toggledWidth: 0
toggledColor: theme.iconBackgroundViewBarToggled
toggled: settingsView.isShown()
imageWidth: 34
imageHeight: 34
source: "qrc:/gpt4all/icons/settings.svg"
Accessible.name: qsTr("Settings")
Accessible.description: qsTr("Settings view for application configuration")
onClicked: {
settingsView.show(0 /*pageToDisplay*/)
}
}
Text {
Layout.topMargin: -20
text: qsTr("Settings")
font.pixelSize: theme.fontSizeLargeCapped
font.bold: true
color: settingsButton.hovered ? settingsButton.backgroundColorHovered : settingsButton.backgroundColor
Layout.preferredWidth: 48
horizontalAlignment: Text.AlignHCenter
TapHandler {
onTapped: function(eventPoint, button) {
settingsView.show(0 /*pageToDisplay*/)
}
}
}
}
ColumnLayout {
id: buttonsLayout
anchors.bottom: parent.bottom
anchors.margins: 0
anchors.bottomMargin: 25
anchors.horizontalCenter: parent.horizontalCenter
Layout.margins: 0
spacing: 22
Rectangle {
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: image.width
Layout.preferredHeight: image.height
color: "transparent"
Image {
id: image
anchors.centerIn: parent
sourceSize: Qt.size(60, 40)
fillMode: Image.PreserveAspectFit
mipmap: true
visible: false
source: "qrc:/gpt4all/icons/nomic_logo.svg"
}
ColorOverlay {
anchors.fill: image
source: image
color: image.hovered ? theme.mutedDarkTextColorHovered : theme.mutedDarkTextColor
TapHandler {
onTapped: function(eventPoint, button) {
Qt.openUrlExternally("https://nomic.ai")
}
}
}
}
}
}
Rectangle {
id: roundedFrame
z: 299
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: viewBar.right
anchors.right: parent.right
anchors.topMargin: 15
anchors.bottomMargin: 15
anchors.rightMargin: 15
radius: 15
border.width: 1
border.color: theme.dividerColor
color: "transparent"
clip: true
}
RectangularGlow {
id: effect
anchors.fill: roundedFrame
glowRadius: 15
spread: 0
color: theme.dividerColor
cornerRadius: 10
opacity: 0.5
}
StackLayout {
id: stackLayout
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: viewBar.right
anchors.right: parent.right
anchors.topMargin: 15
anchors.bottomMargin: 15
anchors.rightMargin: 15
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: roundedFrame.width
height: roundedFrame.height
radius: 15
}
}
HomeView {
id: homeView
Layout.fillWidth: true
Layout.fillHeight: true
shouldShowFirstStart: !hasCheckedFirstStart
function show() {
stackLayout.currentIndex = 0;
}
function isShown() {
return stackLayout.currentIndex === 0
}
Connections {
target: homeView
function onChatViewRequested() {
chatView.show();
}
function onLocalDocsViewRequested() {
localDocsView.show();
}
function onAddModelViewRequested() {
addModelView.show();
}
function onSettingsViewRequested(page) {
settingsView.show(page);
}
}
}
ChatView {
id: chatView
Layout.fillWidth: true
Layout.fillHeight: true
function show() {
stackLayout.currentIndex = 1;
}
function isShown() {
return stackLayout.currentIndex === 1
}
Connections {
target: chatView
function onAddCollectionViewRequested() {
addCollectionView.show();
}
function onAddModelViewRequested() {
addModelView.show();
}
}
}
ModelsView {
id: modelsView
Layout.fillWidth: true
Layout.fillHeight: true
function show() {
stackLayout.currentIndex = 2;
// FIXME This expanded code should be removed and we should be changing the names of
// the classes here in ModelList for the proxy/filter models
ModelList.downloadableModels.expanded = true
}
function isShown() {
return stackLayout.currentIndex === 2
}
Item {
Accessible.name: qsTr("Installed models")
Accessible.description: qsTr("View of installed models")
}
Connections {
target: modelsView
function onAddModelViewRequested() {
addModelView.show();
}
}
}
LocalDocsView {
id: localDocsView
Layout.fillWidth: true
Layout.fillHeight: true
function show() {
stackLayout.currentIndex = 3;
}
function isShown() {
return stackLayout.currentIndex === 3
}
Connections {
target: localDocsView
function onAddCollectionViewRequested() {
addCollectionView.show();
}
}
}
SettingsView {
id: settingsView
Layout.fillWidth: true
Layout.fillHeight: true
function show(page) {
settingsView.pageToDisplay = page;
stackLayout.currentIndex = 4;
}
function isShown() {
return stackLayout.currentIndex === 4
}
}
AddCollectionView {
id: addCollectionView
Layout.fillWidth: true
Layout.fillHeight: true
function show() {
stackLayout.currentIndex = 5;
}
function isShown() {
return stackLayout.currentIndex === 5
}
Connections {
target: addCollectionView
function onLocalDocsViewRequested() {
localDocsView.show();
}
}
}
AddModelView {
id: addModelView
Layout.fillWidth: true
Layout.fillHeight: true
function show() {
stackLayout.currentIndex = 6;
}
function isShown() {
return stackLayout.currentIndex === 6
}
Connections {
target: addModelView
function onModelsViewRequested() {
modelsView.show();
}
}
}
} }
} }

View File

@ -26,10 +26,11 @@
#include <QSettings> #include <QSettings>
#include <QSslConfiguration> #include <QSslConfiguration>
#include <QSslSocket> #include <QSslSocket>
#include <QStringList>
#include <QTextStream> #include <QTextStream>
#include <QTimer> #include <QTimer>
#include <QtLogging>
#include <QUrl> #include <QUrl>
#include <QtLogging>
#include <algorithm> #include <algorithm>
#include <compare> #include <compare>
@ -38,12 +39,11 @@
#include <string> #include <string>
#include <utility> #include <utility>
using namespace Qt::Literals::StringLiterals;
//#define USE_LOCAL_MODELSJSON //#define USE_LOCAL_MODELSJSON
const char * const KNOWN_EMBEDDING_MODELS[] { static const QStringList FILENAME_BLACKLIST { u"gpt4all-nomic-embed-text-v1.rmodel"_s };
"all-MiniLM-L6-v2.gguf2.f16.gguf",
"gpt4all-nomic-embed-text-v1.rmodel",
};
QString ModelInfo::id() const QString ModelInfo::id() const
{ {
@ -339,56 +339,32 @@ bool ModelInfo::shouldSaveMetadata() const
return installed && (isClone() || isDiscovered() || description() == "" /*indicates sideloaded*/); return installed && (isClone() || isDiscovered() || description() == "" /*indicates sideloaded*/);
} }
EmbeddingModels::EmbeddingModels(QObject *parent, bool requireInstalled) QVariantMap ModelInfo::getFields() const
: QSortFilterProxyModel(parent)
{ {
m_requireInstalled = requireInstalled; return {
{ "filename", m_filename },
connect(this, &EmbeddingModels::rowsInserted, this, &EmbeddingModels::countChanged); { "description", m_description },
connect(this, &EmbeddingModels::rowsRemoved, this, &EmbeddingModels::countChanged); { "url", m_url },
connect(this, &EmbeddingModels::modelReset, this, &EmbeddingModels::countChanged); { "quant", m_quant },
connect(this, &EmbeddingModels::layoutChanged, this, &EmbeddingModels::countChanged); { "type", m_type },
} { "isClone", m_isClone },
{ "isDiscovered", m_isDiscovered },
bool EmbeddingModels::filterAcceptsRow(int sourceRow, { "likes", m_likes },
const QModelIndex &sourceParent) const { "downloads", m_downloads },
{ { "recency", m_recency },
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); { "temperature", m_temperature },
bool isEmbeddingModel = sourceModel()->data(index, ModelList::IsEmbeddingModelRole).toBool(); { "topP", m_topP },
bool installed = sourceModel()->data(index, ModelList::InstalledRole).toBool(); { "minP", m_minP },
QString filename = sourceModel()->data(index, ModelList::FilenameRole).toString(); { "topK", m_topK },
auto &known = KNOWN_EMBEDDING_MODELS; { "maxLength", m_maxLength },
if (std::find(known, std::end(known), filename.toStdString()) == std::end(known)) { "promptBatchSize", m_promptBatchSize },
return false; // we are currently not prepared to support other embedding models { "contextLength", m_contextLength },
{ "gpuLayers", m_gpuLayers },
return isEmbeddingModel && (!m_requireInstalled || installed); { "repeatPenalty", m_repeatPenalty },
} { "repeatPenaltyTokens", m_repeatPenaltyTokens },
{ "promptTemplate", m_promptTemplate },
int EmbeddingModels::defaultModelIndex() const { "systemPrompt", m_systemPrompt },
{ };
auto *sourceListModel = qobject_cast<const ModelList*>(sourceModel());
if (!sourceListModel) return -1;
int rows = sourceListModel->rowCount();
for (int i = 0; i < rows; ++i) {
if (filterAcceptsRow(i, sourceListModel->index(i, 0).parent()))
return i;
}
return -1;
}
ModelInfo EmbeddingModels::defaultModelInfo() const
{
auto *sourceListModel = qobject_cast<const ModelList*>(sourceModel());
if (!sourceListModel) return ModelInfo();
int i = defaultModelIndex();
if (i < 0) return ModelInfo();
QModelIndex sourceIndex = sourceListModel->index(i, 0);
auto id = sourceListModel->data(sourceIndex, ModelList::IdRole).toString();
return sourceListModel->modelInfo(id);
} }
InstalledModels::InstalledModels(QObject *parent) InstalledModels::InstalledModels(QObject *parent)
@ -424,13 +400,14 @@ DownloadableModels::DownloadableModels(QObject *parent)
bool DownloadableModels::filterAcceptsRow(int sourceRow, bool DownloadableModels::filterAcceptsRow(int sourceRow,
const QModelIndex &sourceParent) const const QModelIndex &sourceParent) const
{ {
// FIXME We can eliminate the 'expanded' code as the UI no longer uses this
bool withinLimit = sourceRow < (m_expanded ? sourceModel()->rowCount() : m_limit); bool withinLimit = sourceRow < (m_expanded ? sourceModel()->rowCount() : m_limit);
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
bool isDownloadable = !sourceModel()->data(index, ModelList::DescriptionRole).toString().isEmpty(); bool isDownloadable = !sourceModel()->data(index, ModelList::DescriptionRole).toString().isEmpty();
bool isInstalled = !sourceModel()->data(index, ModelList::InstalledRole).toString().isEmpty(); bool isInstalled = sourceModel()->data(index, ModelList::InstalledRole).toBool();
bool isIncomplete = !sourceModel()->data(index, ModelList::IncompleteRole).toString().isEmpty(); bool isIncomplete = sourceModel()->data(index, ModelList::IncompleteRole).toBool();
bool isClone = sourceModel()->data(index, ModelList::IsCloneRole).toBool(); bool isClone = sourceModel()->data(index, ModelList::IsCloneRole).toBool();
return withinLimit && !isClone && (isDownloadable || isInstalled || isIncomplete); return withinLimit && !isClone && !isInstalled && (isDownloadable || isIncomplete);
} }
int DownloadableModels::count() const int DownloadableModels::count() const
@ -468,9 +445,7 @@ ModelList *ModelList::globalInstance()
ModelList::ModelList() ModelList::ModelList()
: QAbstractListModel(nullptr) : QAbstractListModel(nullptr)
, m_embeddingModels(new EmbeddingModels(this, false /* all models */))
, m_installedModels(new InstalledModels(this)) , m_installedModels(new InstalledModels(this))
, m_installedEmbeddingModels(new EmbeddingModels(this, true /* installed models */))
, m_downloadableModels(new DownloadableModels(this)) , m_downloadableModels(new DownloadableModels(this))
, m_asyncModelRequestOngoing(false) , m_asyncModelRequestOngoing(false)
, m_discoverLimit(20) , m_discoverLimit(20)
@ -480,9 +455,7 @@ ModelList::ModelList()
, m_discoverResultsCompleted(0) , m_discoverResultsCompleted(0)
, m_discoverInProgress(false) , m_discoverInProgress(false)
{ {
m_embeddingModels->setSourceModel(this);
m_installedModels->setSourceModel(this); m_installedModels->setSourceModel(this);
m_installedEmbeddingModels->setSourceModel(this);
m_downloadableModels->setSourceModel(this); m_downloadableModels->setSourceModel(this);
connect(MySettings::globalInstance(), &MySettings::modelPathChanged, this, &ModelList::updateModelsFromDirectory); connect(MySettings::globalInstance(), &MySettings::modelPathChanged, this, &ModelList::updateModelsFromDirectory);
@ -552,17 +525,11 @@ const QList<QString> ModelList::userDefaultModelList() const
return models; return models;
} }
int ModelList::defaultEmbeddingModelIndex() const
{
return embeddingModels()->defaultModelIndex();
}
ModelInfo ModelList::defaultModelInfo() const ModelInfo ModelList::defaultModelInfo() const
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
QSettings settings; QSettings settings;
settings.sync();
// The user default model can be set by the user in the settings dialog. The "default" user // The user default model can be set by the user in the settings dialog. The "default" user
// default model is "Application default" which signals we should use the logic here. // default model is "Application default" which signals we should use the logic here.
@ -1153,7 +1120,7 @@ void ModelList::removeInternal(const ModelInfo &model)
QString ModelList::uniqueModelName(const ModelInfo &model) const QString ModelList::uniqueModelName(const ModelInfo &model) const
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
QRegularExpression re("^(.*)~(\\d+)$"); static const QRegularExpression re("^(.*)~(\\d+)$");
QRegularExpressionMatch match = re.match(model.name()); QRegularExpressionMatch match = re.match(model.name());
QString baseName; QString baseName;
if (match.hasMatch()) if (match.hasMatch())
@ -1208,13 +1175,11 @@ void ModelList::updateModelsFromDirectory()
it.next(); it.next();
if (!it.fileInfo().isDir()) { if (!it.fileInfo().isDir()) {
QString filename = it.fileName(); QString filename = it.fileName();
if (filename.endsWith(".txt") && (filename.startsWith("chatgpt-") || filename.startsWith("nomic-"))) { if (filename.startsWith("chatgpt-") && filename.endsWith(".txt")) {
QString apikey; QString apikey;
QString modelname(filename); QString modelname(filename);
modelname.chop(4); // strip ".txt" extension modelname.chop(4); // strip ".txt" extension
if (filename.startsWith("chatgpt-")) { modelname.remove(0, 8); // strip "chatgpt-" prefix
modelname.remove(0, 8); // strip "chatgpt-" prefix
}
QFile file(path + filename); QFile file(path + filename);
if (file.open(QIODevice::ReadWrite)) { if (file.open(QIODevice::ReadWrite)) {
QTextStream in(&file); QTextStream in(&file);
@ -1227,7 +1192,7 @@ void ModelList::updateModelsFromDirectory()
obj.insert("modelName", modelname); obj.insert("modelName", modelname);
QJsonDocument doc(obj); QJsonDocument doc(obj);
auto newfilename = QString("gpt4all-%1.rmodel").arg(modelname); auto newfilename = u"gpt4all-%1.rmodel"_s.arg(modelname);
QFile newfile(path + newfilename); QFile newfile(path + newfilename);
if (newfile.open(QIODevice::ReadWrite)) { if (newfile.open(QIODevice::ReadWrite)) {
QTextStream out(&newfile); QTextStream out(&newfile);
@ -1241,46 +1206,41 @@ void ModelList::updateModelsFromDirectory()
}; };
auto processDirectory = [&](const QString& path) { auto processDirectory = [&](const QString& path) {
QDirIterator it(path, QDirIterator::Subdirectories); QDirIterator it(path, QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) { while (it.hasNext()) {
it.next(); it.next();
if (!it.fileInfo().isDir()) { QString filename = it.fileName();
QString filename = it.fileName(); if (filename.startsWith("incomplete") || FILENAME_BLACKLIST.contains(filename))
continue;
if (!filename.endsWith(".gguf") && !filename.endsWith(".rmodel"))
continue;
if ((filename.endsWith(".gguf") && !filename.startsWith("incomplete")) || filename.endsWith(".rmodel")) { QVector<QString> modelsById;
{
QMutexLocker locker(&m_mutex);
for (ModelInfo *info : m_models)
if (info->filename() == filename)
modelsById.append(info->id());
}
QString filePath = it.filePath(); if (modelsById.isEmpty()) {
QFileInfo info(filePath); if (!contains(filename))
addModel(filename);
modelsById.append(filename);
}
if (!info.exists()) QFileInfo info = it.fileInfo();
continue;
QVector<QString> modelsById; for (const QString &id : modelsById) {
{ QVector<QPair<int, QVariant>> data {
QMutexLocker locker(&m_mutex); { InstalledRole, true },
for (ModelInfo *info : m_models) { FilenameRole, filename },
if (info->filename() == filename) { OnlineRole, filename.endsWith(".rmodel") },
modelsById.append(info->id()); { DirpathRole, info.dir().absolutePath() + "/" },
} { FilesizeRole, toFileSize(info.size()) },
};
if (modelsById.isEmpty()) { updateData(id, data);
if (!contains(filename))
addModel(filename);
modelsById.append(filename);
}
for (const QString &id : modelsById) {
QVector<QPair<int, QVariant>> data {
{ InstalledRole, true },
{ FilenameRole, filename },
{ OnlineRole, filename.endsWith(".rmodel") },
{ DirpathRole, info.dir().absolutePath() + "/" },
{ FilesizeRole, toFileSize(info.size()) },
};
updateData(id, data);
}
}
} }
} }
}; };
@ -1299,9 +1259,9 @@ void ModelList::updateModelsFromDirectory()
void ModelList::updateModelsFromJson() void ModelList::updateModelsFromJson()
{ {
#if defined(USE_LOCAL_MODELSJSON) #if defined(USE_LOCAL_MODELSJSON)
QUrl jsonUrl("file://" + QDir::homePath() + QString("/dev/large_language_models/gpt4all/gpt4all-chat/metadata/models%1.json").arg(MODELS_VERSION)); QUrl jsonUrl("file://" + QDir::homePath() + u"/dev/large_language_models/gpt4all/gpt4all-chat/metadata/models%1.json"_s.arg(MODELS_VERSION));
#else #else
QUrl jsonUrl(QString("http://gpt4all.io/models/models%1.json").arg(MODELS_VERSION)); QUrl jsonUrl(u"http://gpt4all.io/models/models%1.json"_s.arg(MODELS_VERSION));
#endif #endif
QNetworkRequest request(jsonUrl); QNetworkRequest request(jsonUrl);
QSslConfiguration conf = request.sslConfiguration(); QSslConfiguration conf = request.sslConfiguration();
@ -1343,9 +1303,9 @@ void ModelList::updateModelsFromJsonAsync()
emit asyncModelRequestOngoingChanged(); emit asyncModelRequestOngoingChanged();
#if defined(USE_LOCAL_MODELSJSON) #if defined(USE_LOCAL_MODELSJSON)
QUrl jsonUrl("file://" + QDir::homePath() + QString("/dev/large_language_models/gpt4all/gpt4all-chat/metadata/models%1.json").arg(MODELS_VERSION)); QUrl jsonUrl("file://" + QDir::homePath() + u"/dev/large_language_models/gpt4all/gpt4all-chat/metadata/models%1.json"_s.arg(MODELS_VERSION));
#else #else
QUrl jsonUrl(QString("http://gpt4all.io/models/models%1.json").arg(MODELS_VERSION)); QUrl jsonUrl(u"http://gpt4all.io/models/models%1.json"_s.arg(MODELS_VERSION));
#endif #endif
QNetworkRequest request(jsonUrl); QNetworkRequest request(jsonUrl);
QSslConfiguration conf = request.sslConfiguration(); QSslConfiguration conf = request.sslConfiguration();
@ -1383,7 +1343,7 @@ void ModelList::handleModelsJsonDownloadErrorOccurred(QNetworkReply::NetworkErro
if (!reply) if (!reply)
return; return;
qWarning() << QString("ERROR: Modellist download failed with error code \"%1-%2\"") qWarning() << u"ERROR: Modellist download failed with error code \"%1-%2\""_s
.arg(code).arg(reply->errorString()); .arg(code).arg(reply->errorString());
} }
@ -1399,7 +1359,8 @@ void ModelList::updateDataForSettings()
emit dataChanged(index(0, 0), index(m_models.size() - 1, 0)); emit dataChanged(index(0, 0), index(m_models.size() - 1, 0));
} }
static std::strong_ordering compareVersions(const QString &a, const QString &b) { static std::strong_ordering compareVersions(const QString &a, const QString &b)
{
QStringList aParts = a.split('.'); QStringList aParts = a.split('.');
QStringList bParts = b.split('.'); QStringList bParts = b.split('.');
@ -1450,8 +1411,8 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
QString versionRemoved = obj["removedIn"].toString(); QString versionRemoved = obj["removedIn"].toString();
QString url = obj["url"].toString(); QString url = obj["url"].toString();
QByteArray modelHash = obj["md5sum"].toString().toLatin1(); QByteArray modelHash = obj["md5sum"].toString().toLatin1();
bool isDefault = obj.contains("isDefault") && obj["isDefault"] == QString("true"); bool isDefault = obj.contains("isDefault") && obj["isDefault"] == u"true"_s;
bool disableGUI = obj.contains("disableGUI") && obj["disableGUI"] == QString("true"); bool disableGUI = obj.contains("disableGUI") && obj["disableGUI"] == u"true"_s;
QString description = obj["description"].toString(); QString description = obj["description"].toString();
QString order = obj["order"].toString(); QString order = obj["order"].toString();
int ramrequired = obj["ramrequired"].toString().toInt(); int ramrequired = obj["ramrequired"].toString().toInt();
@ -1586,8 +1547,8 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
}; };
updateData(id, data); updateData(id, data);
} }
const QString mistralDesc = tr("<ul><li>Requires personal Mistral API key.</li><li>WARNING: Will send" const QString mistralDesc = tr("<ul><li>Requires personal Mistral API key.</li><li>WARNING: Will send"
" your chats to Mistral!</li><li>Your API key will be stored on disk</li><li>Will only be used" " your chats to Mistral!</li><li>Your API key will be stored on disk</li><li>Will only be used"
" to communicate with Mistral</li><li>You can apply for an API key" " to communicate with Mistral</li><li>You can apply for an API key"
" <a href=\"https://console.mistral.ai/user/api-keys\">here</a>.</li>"); " <a href=\"https://console.mistral.ai/user/api-keys\">here</a>.</li>");
@ -1642,7 +1603,7 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
}; };
updateData(id, data); updateData(id, data);
} }
{ {
const QString modelName = "Mistral Medium API"; const QString modelName = "Mistral Medium API";
const QString id = modelName; const QString id = modelName;
@ -1668,38 +1629,6 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
}; };
updateData(id, data); updateData(id, data);
} }
{
const QString nomicEmbedDesc = tr("<ul><li>For use with LocalDocs feature</li>"
"<li>Used for retrieval augmented generation (RAG)</li>"
"<li>Requires personal Nomic API key.</li>"
"<li>WARNING: Will send your localdocs to Nomic Atlas!</li>"
"<li>You can apply for an API key <a href=\"https://atlas.nomic.ai/\">with Nomic Atlas.</a></li>");
const QString modelName = "Nomic Embed";
const QString id = modelName;
const QString modelFilename = "gpt4all-nomic-embed-text-v1.rmodel";
if (contains(modelFilename))
changeId(modelFilename, id);
if (!contains(id))
addModel(id);
QVector<QPair<int, QVariant>> data {
{ ModelList::NameRole, modelName },
{ ModelList::FilenameRole, modelFilename },
{ ModelList::FilesizeRole, "minimal" },
{ ModelList::OnlineRole, true },
{ ModelList::IsEmbeddingModelRole, true },
{ ModelList::DescriptionRole,
tr("<strong>LocalDocs Nomic Atlas Embed</strong><br>") + nomicEmbedDesc },
{ ModelList::RequiresVersionRole, "2.6.3" },
{ ModelList::OrderRole, "na" },
{ ModelList::RamrequiredRole, 0 },
{ ModelList::ParametersRole, "?" },
{ ModelList::QuantRole, "NA" },
{ ModelList::TypeRole, "Bert" },
};
updateData(id, data);
}
} }
void ModelList::updateDiscoveredInstalled(const ModelInfo &info) void ModelList::updateDiscoveredInstalled(const ModelInfo &info)
@ -1723,9 +1652,8 @@ void ModelList::updateDiscoveredInstalled(const ModelInfo &info)
void ModelList::updateModelsFromSettings() void ModelList::updateModelsFromSettings()
{ {
QSettings settings; QSettings settings;
settings.sync();
QStringList groups = settings.childGroups(); QStringList groups = settings.childGroups();
for (const QString g : groups) { for (const QString &g: groups) {
if (!g.startsWith("model-")) if (!g.startsWith("model-"))
continue; continue;
@ -1913,7 +1841,7 @@ void ModelList::discoverSearch(const QString &search)
m_discoverNumberOfResults = 0; m_discoverNumberOfResults = 0;
m_discoverResultsCompleted = 0; m_discoverResultsCompleted = 0;
discoverProgressChanged(); emit discoverProgressChanged();
if (search.isEmpty()) { if (search.isEmpty()) {
return; return;
@ -1922,9 +1850,10 @@ void ModelList::discoverSearch(const QString &search)
m_discoverInProgress = true; m_discoverInProgress = true;
emit discoverInProgressChanged(); emit discoverInProgressChanged();
QStringList searchParams = search.split(QRegularExpression("\\s+")); // split by whitespace static const QRegularExpression wsRegex("\\s+");
QString searchString = QString("search=%1&").arg(searchParams.join('+')); QStringList searchParams = search.split(wsRegex); // split by whitespace
QString limitString = m_discoverLimit > 0 ? QString("limit=%1&").arg(m_discoverLimit) : QString(); QString searchString = u"search=%1&"_s.arg(searchParams.join('+'));
QString limitString = m_discoverLimit > 0 ? u"limit=%1&"_s.arg(m_discoverLimit) : QString();
QString sortString; QString sortString;
switch (m_discoverSort) { switch (m_discoverSort) {
@ -1937,9 +1866,10 @@ void ModelList::discoverSearch(const QString &search)
sortString = "sort=lastModified&"; break; sortString = "sort=lastModified&"; break;
} }
QString directionString = !sortString.isEmpty() ? QString("direction=%1&").arg(m_discoverSortDirection) : QString(); QString directionString = !sortString.isEmpty() ? u"direction=%1&"_s.arg(m_discoverSortDirection) : QString();
QUrl hfUrl(QString("https://huggingface.co/api/models?filter=gguf&%1%2%3%4full=true&config=true").arg(searchString).arg(limitString).arg(sortString).arg(directionString)); QUrl hfUrl(u"https://huggingface.co/api/models?filter=gguf&%1%2%3%4full=true&config=true"_s
.arg(searchString, limitString, sortString, directionString));
QNetworkRequest request(hfUrl); QNetworkRequest request(hfUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
@ -1965,7 +1895,7 @@ void ModelList::handleDiscoveryErrorOccurred(QNetworkReply::NetworkError code)
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender()); QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
if (!reply) if (!reply)
return; return;
qWarning() << QString("ERROR: Discovery failed with error code \"%1-%2\"") qWarning() << u"ERROR: Discovery failed with error code \"%1-%2\""_s
.arg(code).arg(reply->errorString()).toStdString(); .arg(code).arg(reply->errorString()).toStdString();
} }
@ -2005,7 +1935,7 @@ void ModelList::parseDiscoveryJsonFile(const QByteArray &jsonData)
qWarning() << "ERROR: Couldn't parse: " << jsonData << err.errorString(); qWarning() << "ERROR: Couldn't parse: " << jsonData << err.errorString();
m_discoverNumberOfResults = 0; m_discoverNumberOfResults = 0;
m_discoverResultsCompleted = 0; m_discoverResultsCompleted = 0;
discoverProgressChanged(); emit discoverProgressChanged();
m_discoverInProgress = false; m_discoverInProgress = false;
emit discoverInProgressChanged(); emit discoverInProgressChanged();
return; return;
@ -2045,7 +1975,7 @@ void ModelList::parseDiscoveryJsonFile(const QByteArray &jsonData)
QString filename = file.second; QString filename = file.second;
++m_discoverNumberOfResults; ++m_discoverNumberOfResults;
QUrl url(QString("https://huggingface.co/%1/resolve/main/%2").arg(repo_id).arg(filename)); QUrl url(u"https://huggingface.co/%1/resolve/main/%2"_s.arg(repo_id, filename));
QNetworkRequest request(url); QNetworkRequest request(url);
request.setRawHeader("Accept-Encoding", "identity"); request.setRawHeader("Accept-Encoding", "identity");
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy); request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy);
@ -2084,21 +2014,16 @@ void ModelList::handleDiscoveryItemFinished()
QJsonObject config = obj["config"].toObject(); QJsonObject config = obj["config"].toObject();
QString type = config["model_type"].toString(); QString type = config["model_type"].toString();
QByteArray repoCommitHeader = reply->rawHeader("X-Repo-Commit"); // QByteArray repoCommitHeader = reply->rawHeader("X-Repo-Commit");
QByteArray linkedSizeHeader = reply->rawHeader("X-Linked-Size"); QByteArray linkedSizeHeader = reply->rawHeader("X-Linked-Size");
QByteArray linkedEtagHeader = reply->rawHeader("X-Linked-Etag"); QByteArray linkedEtagHeader = reply->rawHeader("X-Linked-Etag");
// For some reason these seem to contain quotation marks ewww // For some reason these seem to contain quotation marks ewww
linkedEtagHeader.replace("\"", ""); linkedEtagHeader.replace("\"", "");
linkedEtagHeader.replace("\'", ""); linkedEtagHeader.replace("\'", "");
QString locationHeader = reply->header(QNetworkRequest::LocationHeader).toString(); // QString locationHeader = reply->header(QNetworkRequest::LocationHeader).toString();
QString repoCommit = QString::fromUtf8(repoCommitHeader);
QString linkedSize = QString::fromUtf8(linkedSizeHeader);
QString linkedEtag = QString::fromUtf8(linkedEtagHeader);
QString modelFilename = reply->request().attribute(QNetworkRequest::UserMax).toString(); QString modelFilename = reply->request().attribute(QNetworkRequest::UserMax).toString();
QString modelFilesize = linkedSize; QString modelFilesize = ModelList::toFileSize(QString(linkedSizeHeader).toULongLong());
modelFilesize = ModelList::toFileSize(modelFilesize.toULongLong());
QString description = tr("<strong>Created by %1.</strong><br><ul>" QString description = tr("<strong>Created by %1.</strong><br><ul>"
"<li>Published on %2." "<li>Published on %2."
@ -2155,6 +2080,6 @@ void ModelList::handleDiscoveryItemErrorOccurred(QNetworkReply::NetworkError cod
if (!reply) if (!reply)
return; return;
qWarning() << QString("ERROR: Discovery item failed with error code \"%1-%2\"") qWarning() << u"ERROR: Discovery item failed with error code \"%1-%2\""_s
.arg(code).arg(reply->errorString()).toStdString(); .arg(code).arg(reply->errorString()).toStdString();
} }

View File

@ -20,6 +20,8 @@
#include <QtGlobal> #include <QtGlobal>
#include <QtQml> #include <QtQml>
using namespace Qt::Literals::StringLiterals;
struct ModelInfo { struct ModelInfo {
Q_GADGET Q_GADGET
Q_PROPERTY(QString id READ id WRITE setId) Q_PROPERTY(QString id READ id WRITE setId)
@ -169,6 +171,8 @@ public:
bool shouldSaveMetadata() const; bool shouldSaveMetadata() const;
private: private:
QVariantMap getFields() const;
QString m_id; QString m_id;
QString m_name; QString m_name;
QString m_filename; QString m_filename;
@ -199,28 +203,6 @@ private:
}; };
Q_DECLARE_METATYPE(ModelInfo) Q_DECLARE_METATYPE(ModelInfo)
class EmbeddingModels : public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
EmbeddingModels(QObject *parent, bool requireInstalled);
int count() const { return rowCount(); }
int defaultModelIndex() const;
ModelInfo defaultModelInfo() const;
Q_SIGNALS:
void countChanged();
void defaultModelIndexChanged();
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
private:
bool m_requireInstalled;
};
class InstalledModels : public QSortFilterProxyModel class InstalledModels : public QSortFilterProxyModel
{ {
Q_OBJECT Q_OBJECT
@ -269,8 +251,6 @@ class ModelList : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged) Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(int defaultEmbeddingModelIndex READ defaultEmbeddingModelIndex)
Q_PROPERTY(EmbeddingModels* installedEmbeddingModels READ installedEmbeddingModels NOTIFY installedEmbeddingModelsChanged)
Q_PROPERTY(InstalledModels* installedModels READ installedModels NOTIFY installedModelsChanged) Q_PROPERTY(InstalledModels* installedModels READ installedModels NOTIFY installedModelsChanged)
Q_PROPERTY(DownloadableModels* downloadableModels READ downloadableModels NOTIFY downloadableModelsChanged) Q_PROPERTY(DownloadableModels* downloadableModels READ downloadableModels NOTIFY downloadableModelsChanged)
Q_PROPERTY(QList<QString> userDefaultModelList READ userDefaultModelList NOTIFY userDefaultModelListChanged) Q_PROPERTY(QList<QString> userDefaultModelList READ userDefaultModelList NOTIFY userDefaultModelListChanged)
@ -408,7 +388,6 @@ public:
Q_INVOKABLE void removeClone(const ModelInfo &model); Q_INVOKABLE void removeClone(const ModelInfo &model);
Q_INVOKABLE void removeInstalled(const ModelInfo &model); Q_INVOKABLE void removeInstalled(const ModelInfo &model);
ModelInfo defaultModelInfo() const; ModelInfo defaultModelInfo() const;
int defaultEmbeddingModelIndex() const;
void addModel(const QString &id); void addModel(const QString &id);
void changeId(const QString &oldId, const QString &newId); void changeId(const QString &oldId, const QString &newId);
@ -416,20 +395,18 @@ public:
const QList<ModelInfo> exportModelList() const; const QList<ModelInfo> exportModelList() const;
const QList<QString> userDefaultModelList() const; const QList<QString> userDefaultModelList() const;
EmbeddingModels *embeddingModels() const { return m_embeddingModels; }
EmbeddingModels *installedEmbeddingModels() const { return m_installedEmbeddingModels; }
InstalledModels *installedModels() const { return m_installedModels; } InstalledModels *installedModels() const { return m_installedModels; }
DownloadableModels *downloadableModels() const { return m_downloadableModels; } DownloadableModels *downloadableModels() const { return m_downloadableModels; }
static inline QString toFileSize(quint64 sz) { static inline QString toFileSize(quint64 sz) {
if (sz < 1024) { if (sz < 1024) {
return QString("%1 bytes").arg(sz); return u"%1 bytes"_s.arg(sz);
} else if (sz < 1024 * 1024) { } else if (sz < 1024 * 1024) {
return QString("%1 KB").arg(qreal(sz) / 1024, 0, 'g', 3); return u"%1 KB"_s.arg(qreal(sz) / 1024, 0, 'g', 3);
} else if (sz < 1024 * 1024 * 1024) { } else if (sz < 1024 * 1024 * 1024) {
return QString("%1 MB").arg(qreal(sz) / (1024 * 1024), 0, 'g', 3); return u"%1 MB"_s.arg(qreal(sz) / (1024 * 1024), 0, 'g', 3);
} else { } else {
return QString("%1 GB").arg(qreal(sz) / (1024 * 1024 * 1024), 0, 'g', 3); return u"%1 GB"_s.arg(qreal(sz) / (1024 * 1024 * 1024), 0, 'g', 3);
} }
} }
@ -455,7 +432,6 @@ public:
Q_SIGNALS: Q_SIGNALS:
void countChanged(); void countChanged();
void installedEmbeddingModelsChanged();
void installedModelsChanged(); void installedModelsChanged();
void downloadableModelsChanged(); void downloadableModelsChanged();
void userDefaultModelListChanged(); void userDefaultModelListChanged();
@ -494,8 +470,6 @@ private:
private: private:
mutable QMutex m_mutex; mutable QMutex m_mutex;
QNetworkAccessManager m_networkManager; QNetworkAccessManager m_networkManager;
EmbeddingModels *m_embeddingModels;
EmbeddingModels *m_installedEmbeddingModels;
InstalledModels *m_installedModels; InstalledModels *m_installedModels;
DownloadableModels *m_downloadableModels; DownloadableModels *m_downloadableModels;
QList<ModelInfo*> m_models; QList<ModelInfo*> m_models;

File diff suppressed because it is too large Load Diff

View File

@ -5,10 +5,13 @@
#include <QDateTime> #include <QDateTime>
#include <QObject> #include <QObject>
#include <QSettings>
#include <QString> #include <QString>
#include <QStringList>
#include <QVector> #include <QVector>
#include <cstdint> #include <cstdint>
#include <optional>
class MySettings : public QObject class MySettings : public QObject
{ {
@ -25,6 +28,9 @@ class MySettings : public QObject
Q_PROPERTY(int localDocsChunkSize READ localDocsChunkSize WRITE setLocalDocsChunkSize NOTIFY localDocsChunkSizeChanged) Q_PROPERTY(int localDocsChunkSize READ localDocsChunkSize WRITE setLocalDocsChunkSize NOTIFY localDocsChunkSizeChanged)
Q_PROPERTY(int localDocsRetrievalSize READ localDocsRetrievalSize WRITE setLocalDocsRetrievalSize NOTIFY localDocsRetrievalSizeChanged) Q_PROPERTY(int localDocsRetrievalSize READ localDocsRetrievalSize WRITE setLocalDocsRetrievalSize NOTIFY localDocsRetrievalSizeChanged)
Q_PROPERTY(bool localDocsShowReferences READ localDocsShowReferences WRITE setLocalDocsShowReferences NOTIFY localDocsShowReferencesChanged) Q_PROPERTY(bool localDocsShowReferences READ localDocsShowReferences WRITE setLocalDocsShowReferences NOTIFY localDocsShowReferencesChanged)
Q_PROPERTY(QStringList localDocsFileExtensions READ localDocsFileExtensions WRITE setLocalDocsFileExtensions NOTIFY localDocsFileExtensionsChanged)
Q_PROPERTY(bool localDocsUseRemoteEmbed READ localDocsUseRemoteEmbed WRITE setLocalDocsUseRemoteEmbed NOTIFY localDocsUseRemoteEmbedChanged)
Q_PROPERTY(QString localDocsNomicAPIKey READ localDocsNomicAPIKey WRITE setLocalDocsNomicAPIKey NOTIFY localDocsNomicAPIKeyChanged)
Q_PROPERTY(QString networkAttribution READ networkAttribution WRITE setNetworkAttribution NOTIFY networkAttributionChanged) Q_PROPERTY(QString networkAttribution READ networkAttribution WRITE setNetworkAttribution NOTIFY networkAttributionChanged)
Q_PROPERTY(bool networkIsActive READ networkIsActive WRITE setNetworkIsActive NOTIFY networkIsActiveChanged) Q_PROPERTY(bool networkIsActive READ networkIsActive WRITE setNetworkIsActive NOTIFY networkIsActiveChanged)
Q_PROPERTY(bool networkUsageStatsActive READ networkUsageStatsActive WRITE setNetworkUsageStatsActive NOTIFY networkUsageStatsActiveChanged) Q_PROPERTY(bool networkUsageStatsActive READ networkUsageStatsActive WRITE setNetworkUsageStatsActive NOTIFY networkUsageStatsActiveChanged)
@ -36,80 +42,80 @@ public:
static MySettings *globalInstance(); static MySettings *globalInstance();
// Restore methods // Restore methods
Q_INVOKABLE void restoreModelDefaults(const ModelInfo &model); Q_INVOKABLE void restoreModelDefaults(const ModelInfo &info);
Q_INVOKABLE void restoreApplicationDefaults(); Q_INVOKABLE void restoreApplicationDefaults();
Q_INVOKABLE void restoreLocalDocsDefaults(); Q_INVOKABLE void restoreLocalDocsDefaults();
// Model/Character settings // Model/Character settings
void eraseModel(const ModelInfo &m); void eraseModel(const ModelInfo &info);
QString modelName(const ModelInfo &m) const; QString modelName(const ModelInfo &info) const;
Q_INVOKABLE void setModelName(const ModelInfo &m, const QString &name, bool force = false); Q_INVOKABLE void setModelName(const ModelInfo &info, const QString &name, bool force = false);
QString modelFilename(const ModelInfo &m) const; QString modelFilename(const ModelInfo &info) const;
Q_INVOKABLE void setModelFilename(const ModelInfo &m, const QString &filename, bool force = false); Q_INVOKABLE void setModelFilename(const ModelInfo &info, const QString &filename, bool force = false);
QString modelDescription(const ModelInfo &m) const; QString modelDescription(const ModelInfo &info) const;
void setModelDescription(const ModelInfo &m, const QString &d, bool force = false); void setModelDescription(const ModelInfo &info, const QString &value, bool force = false);
QString modelUrl(const ModelInfo &m) const; QString modelUrl(const ModelInfo &info) const;
void setModelUrl(const ModelInfo &m, const QString &u, bool force = false); void setModelUrl(const ModelInfo &info, const QString &value, bool force = false);
QString modelQuant(const ModelInfo &m) const; QString modelQuant(const ModelInfo &info) const;
void setModelQuant(const ModelInfo &m, const QString &q, bool force = false); void setModelQuant(const ModelInfo &info, const QString &value, bool force = false);
QString modelType(const ModelInfo &m) const; QString modelType(const ModelInfo &info) const;
void setModelType(const ModelInfo &m, const QString &t, bool force = false); void setModelType(const ModelInfo &info, const QString &value, bool force = false);
bool modelIsClone(const ModelInfo &m) const; bool modelIsClone(const ModelInfo &info) const;
void setModelIsClone(const ModelInfo &m, bool b, bool force = false); void setModelIsClone(const ModelInfo &info, bool value, bool force = false);
bool modelIsDiscovered(const ModelInfo &m) const; bool modelIsDiscovered(const ModelInfo &info) const;
void setModelIsDiscovered(const ModelInfo &m, bool b, bool force = false); void setModelIsDiscovered(const ModelInfo &info, bool value, bool force = false);
int modelLikes(const ModelInfo &m) const; int modelLikes(const ModelInfo &info) const;
void setModelLikes(const ModelInfo &m, int l, bool force = false); void setModelLikes(const ModelInfo &info, int value, bool force = false);
int modelDownloads(const ModelInfo &m) const; int modelDownloads(const ModelInfo &info) const;
void setModelDownloads(const ModelInfo &m, int d, bool force = false); void setModelDownloads(const ModelInfo &info, int value, bool force = false);
QDateTime modelRecency(const ModelInfo &m) const; QDateTime modelRecency(const ModelInfo &info) const;
void setModelRecency(const ModelInfo &m, const QDateTime &r, bool force = false); void setModelRecency(const ModelInfo &info, const QDateTime &value, bool force = false);
double modelTemperature(const ModelInfo &m) const; double modelTemperature(const ModelInfo &info) const;
Q_INVOKABLE void setModelTemperature(const ModelInfo &m, double t, bool force = false); Q_INVOKABLE void setModelTemperature(const ModelInfo &info, double value, bool force = false);
double modelTopP(const ModelInfo &m) const; double modelTopP(const ModelInfo &info) const;
Q_INVOKABLE void setModelTopP(const ModelInfo &m, double p, bool force = false); Q_INVOKABLE void setModelTopP(const ModelInfo &info, double value, bool force = false);
double modelMinP(const ModelInfo &m) const; double modelMinP(const ModelInfo &info) const;
Q_INVOKABLE void setModelMinP(const ModelInfo &m, double p, bool force = false); Q_INVOKABLE void setModelMinP(const ModelInfo &info, double value, bool force = false);
int modelTopK(const ModelInfo &m) const; int modelTopK(const ModelInfo &info) const;
Q_INVOKABLE void setModelTopK(const ModelInfo &m, int k, bool force = false); Q_INVOKABLE void setModelTopK(const ModelInfo &info, int value, bool force = false);
int modelMaxLength(const ModelInfo &m) const; int modelMaxLength(const ModelInfo &info) const;
Q_INVOKABLE void setModelMaxLength(const ModelInfo &m, int l, bool force = false); Q_INVOKABLE void setModelMaxLength(const ModelInfo &info, int value, bool force = false);
int modelPromptBatchSize(const ModelInfo &m) const; int modelPromptBatchSize(const ModelInfo &info) const;
Q_INVOKABLE void setModelPromptBatchSize(const ModelInfo &m, int s, bool force = false); Q_INVOKABLE void setModelPromptBatchSize(const ModelInfo &info, int value, bool force = false);
double modelRepeatPenalty(const ModelInfo &m) const; double modelRepeatPenalty(const ModelInfo &info) const;
Q_INVOKABLE void setModelRepeatPenalty(const ModelInfo &m, double p, bool force = false); Q_INVOKABLE void setModelRepeatPenalty(const ModelInfo &info, double value, bool force = false);
int modelRepeatPenaltyTokens(const ModelInfo &m) const; int modelRepeatPenaltyTokens(const ModelInfo &info) const;
Q_INVOKABLE void setModelRepeatPenaltyTokens(const ModelInfo &m, int t, bool force = false); Q_INVOKABLE void setModelRepeatPenaltyTokens(const ModelInfo &info, int value, bool force = false);
QString modelPromptTemplate(const ModelInfo &m) const; QString modelPromptTemplate(const ModelInfo &info) const;
Q_INVOKABLE void setModelPromptTemplate(const ModelInfo &m, const QString &t, bool force = false); Q_INVOKABLE void setModelPromptTemplate(const ModelInfo &info, const QString &value, bool force = false);
QString modelSystemPrompt(const ModelInfo &m) const; QString modelSystemPrompt(const ModelInfo &info) const;
Q_INVOKABLE void setModelSystemPrompt(const ModelInfo &m, const QString &p, bool force = false); Q_INVOKABLE void setModelSystemPrompt(const ModelInfo &info, const QString &value, bool force = false);
int modelContextLength(const ModelInfo &m) const; int modelContextLength(const ModelInfo &info) const;
Q_INVOKABLE void setModelContextLength(const ModelInfo &m, int s, bool force = false); Q_INVOKABLE void setModelContextLength(const ModelInfo &info, int value, bool force = false);
int modelGpuLayers(const ModelInfo &m) const; int modelGpuLayers(const ModelInfo &info) const;
Q_INVOKABLE void setModelGpuLayers(const ModelInfo &m, int s, bool force = false); Q_INVOKABLE void setModelGpuLayers(const ModelInfo &info, int value, bool force = false);
// Application settings // Application settings
int threadCount() const; int threadCount() const;
void setThreadCount(int c); void setThreadCount(int value);
bool saveChatsContext() const; bool saveChatsContext() const;
void setSaveChatsContext(bool b); void setSaveChatsContext(bool value);
bool serverChat() const; bool serverChat() const;
void setServerChat(bool b); void setServerChat(bool value);
QString modelPath() const; QString modelPath();
void setModelPath(const QString &p); void setModelPath(const QString &value);
QString userDefaultModel() const; QString userDefaultModel() const;
void setUserDefaultModel(const QString &u); void setUserDefaultModel(const QString &value);
QString chatTheme() const; QString chatTheme() const;
void setChatTheme(const QString &u); void setChatTheme(const QString &value);
QString fontSize() const; QString fontSize() const;
void setFontSize(const QString &u); void setFontSize(const QString &value);
bool forceMetal() const; bool forceMetal() const;
void setForceMetal(bool b); void setForceMetal(bool value);
QString device() const; QString device();
void setDevice(const QString &u); void setDevice(const QString &value);
int32_t contextLength() const; int32_t contextLength() const;
void setContextLength(int32_t value); void setContextLength(int32_t value);
int32_t gpuLayers() const; int32_t gpuLayers() const;
@ -117,46 +123,53 @@ public:
// Release/Download settings // Release/Download settings
QString lastVersionStarted() const; QString lastVersionStarted() const;
void setLastVersionStarted(const QString &v); void setLastVersionStarted(const QString &value);
// Localdocs settings // Localdocs settings
int localDocsChunkSize() const; int localDocsChunkSize() const;
void setLocalDocsChunkSize(int s); void setLocalDocsChunkSize(int value);
int localDocsRetrievalSize() const; int localDocsRetrievalSize() const;
void setLocalDocsRetrievalSize(int s); void setLocalDocsRetrievalSize(int value);
bool localDocsShowReferences() const; bool localDocsShowReferences() const;
void setLocalDocsShowReferences(bool b); void setLocalDocsShowReferences(bool value);
QStringList localDocsFileExtensions() const;
void setLocalDocsFileExtensions(const QStringList &value);
bool localDocsUseRemoteEmbed() const;
void setLocalDocsUseRemoteEmbed(bool value);
QString localDocsNomicAPIKey() const;
void setLocalDocsNomicAPIKey(const QString &value);
// Network settings // Network settings
QString networkAttribution() const; QString networkAttribution() const;
void setNetworkAttribution(const QString &a); void setNetworkAttribution(const QString &value);
bool networkIsActive() const; bool networkIsActive() const;
Q_INVOKABLE bool isNetworkIsActiveSet() const; Q_INVOKABLE bool isNetworkIsActiveSet() const;
void setNetworkIsActive(bool b); void setNetworkIsActive(bool value);
bool networkUsageStatsActive() const; bool networkUsageStatsActive() const;
Q_INVOKABLE bool isNetworkUsageStatsActiveSet() const; Q_INVOKABLE bool isNetworkUsageStatsActiveSet() const;
void setNetworkUsageStatsActive(bool b); void setNetworkUsageStatsActive(bool value);
int networkPort() const; int networkPort() const;
void setNetworkPort(int c); void setNetworkPort(int value);
QVector<QString> deviceList() const; QVector<QString> deviceList() const;
void setDeviceList(const QVector<QString> &deviceList); void setDeviceList(const QVector<QString> &value);
Q_SIGNALS: Q_SIGNALS:
void nameChanged(const ModelInfo &model); void nameChanged(const ModelInfo &info);
void filenameChanged(const ModelInfo &model); void filenameChanged(const ModelInfo &info);
void temperatureChanged(const ModelInfo &model); void descriptionChanged(const ModelInfo &info);
void topPChanged(const ModelInfo &model); void temperatureChanged(const ModelInfo &info);
void minPChanged(const ModelInfo &model); void topPChanged(const ModelInfo &info);
void topKChanged(const ModelInfo &model); void minPChanged(const ModelInfo &info);
void maxLengthChanged(const ModelInfo &model); void topKChanged(const ModelInfo &info);
void promptBatchSizeChanged(const ModelInfo &model); void maxLengthChanged(const ModelInfo &info);
void contextLengthChanged(const ModelInfo &model); void promptBatchSizeChanged(const ModelInfo &info);
void gpuLayersChanged(const ModelInfo &model); void contextLengthChanged(const ModelInfo &info);
void repeatPenaltyChanged(const ModelInfo &model); void gpuLayersChanged(const ModelInfo &info);
void repeatPenaltyTokensChanged(const ModelInfo &model); void repeatPenaltyChanged(const ModelInfo &info);
void promptTemplateChanged(const ModelInfo &model); void repeatPenaltyTokensChanged(const ModelInfo &info);
void systemPromptChanged(const ModelInfo &model); void promptTemplateChanged(const ModelInfo &info);
void systemPromptChanged(const ModelInfo &info);
void threadCountChanged(); void threadCountChanged();
void saveChatsContextChanged(); void saveChatsContextChanged();
void serverChatChanged(); void serverChatChanged();
@ -169,6 +182,9 @@ Q_SIGNALS:
void localDocsChunkSizeChanged(); void localDocsChunkSizeChanged();
void localDocsRetrievalSizeChanged(); void localDocsRetrievalSizeChanged();
void localDocsShowReferencesChanged(); void localDocsShowReferencesChanged();
void localDocsFileExtensionsChanged();
void localDocsUseRemoteEmbedChanged();
void localDocsNomicAPIKeyChanged();
void networkAttributionChanged(); void networkAttributionChanged();
void networkIsActiveChanged(); void networkIsActiveChanged();
void networkPortChanged(); void networkPortChanged();
@ -178,6 +194,7 @@ Q_SIGNALS:
void deviceListChanged(); void deviceListChanged();
private: private:
QSettings m_settings;
bool m_forceMetal; bool m_forceMetal;
QVector<QString> m_deviceList; QVector<QString> m_deviceList;
@ -185,6 +202,12 @@ private:
explicit MySettings(); explicit MySettings();
~MySettings() {} ~MySettings() {}
friend class MyPrivateSettings; friend class MyPrivateSettings;
QVariant getBasicSetting(const QString &name) const;
void setBasicSetting(const QString &name, const QVariant &value, std::optional<QString> signal = std::nullopt);
QVariant getModelSetting(const QString &name, const ModelInfo &info) const;
void setModelSetting(const QString &name, const ModelInfo &info, const QVariant &value, bool force,
bool signal = false);
}; };
#endif // MYSETTINGS_H #endif // MYSETTINGS_H

View File

@ -36,6 +36,8 @@
#include <cstring> #include <cstring>
#include <utility> #include <utility>
using namespace Qt::Literals::StringLiterals;
//#define DEBUG //#define DEBUG
static const char MIXPANEL_TOKEN[] = "ce362e568ddaee16ed243eaffb5860a2"; static const char MIXPANEL_TOKEN[] = "ce362e568ddaee16ed243eaffb5860a2";
@ -43,7 +45,8 @@ static const char MIXPANEL_TOKEN[] = "ce362e568ddaee16ed243eaffb5860a2";
#if defined(Q_OS_MAC) #if defined(Q_OS_MAC)
#include <sys/sysctl.h> #include <sys/sysctl.h>
static QString 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);
@ -53,14 +56,16 @@ static QString getCPUModel() {
#elif defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86) #elif defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86)
#ifndef _MSC_VER #ifndef _MSC_VER
static void get_cpuid(int level, int *regs) { static void get_cpuid(int level, int *regs)
{
asm volatile("cpuid" : "=a" (regs[0]), "=b" (regs[1]), "=c" (regs[2]), "=d" (regs[3]) : "0" (level) : "memory"); asm volatile("cpuid" : "=a" (regs[0]), "=b" (regs[1]), "=c" (regs[2]), "=d" (regs[3]) : "0" (level) : "memory");
} }
#else #else
#define get_cpuid(level, regs) __cpuid(regs, level) #define get_cpuid(level, regs) __cpuid(regs, level)
#endif #endif
static QString getCPUModel() { static QString getCPUModel()
{
int regs[12]; int regs[12];
// EAX=800000000h: Get Highest Extended Function Implemented // EAX=800000000h: Get Highest Extended Function Implemented
@ -98,10 +103,8 @@ Network::Network()
: QObject{nullptr} : QObject{nullptr}
{ {
QSettings settings; QSettings settings;
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();
m_sessionId = generateUniqueId(); m_sessionId = generateUniqueId();
// allow sendMixpanel to be called from any thread // allow sendMixpanel to be called from any thread
@ -275,7 +278,7 @@ void Network::sendStartup()
const auto *display = QGuiApplication::primaryScreen(); const auto *display = QGuiApplication::primaryScreen();
trackEvent("startup", { trackEvent("startup", {
{"$screen_dpi", std::round(display->physicalDotsPerInch())}, {"$screen_dpi", std::round(display->physicalDotsPerInch())},
{"display", QString("%1x%2").arg(display->size().width()).arg(display->size().height())}, {"display", u"%1x%2"_s.arg(display->size().width()).arg(display->size().height())},
{"ram", LLM::globalInstance()->systemTotalRAMInGB()}, {"ram", LLM::globalInstance()->systemTotalRAMInGB()},
{"cpu", getCPUModel()}, {"cpu", getCPUModel()},
{"cpu_supports_avx2", LLModel::Implementation::cpuSupportsAVX2()}, {"cpu_supports_avx2", LLModel::Implementation::cpuSupportsAVX2()},

70
gpt4all-chat/oscompat.cpp Normal file
View File

@ -0,0 +1,70 @@
#include "oscompat.h"
#include <QByteArray>
#include <QString>
#include <QtGlobal>
#ifdef Q_OS_WIN32
# define WIN32_LEAN_AND_MEAN
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <windows.h>
# include <errno.h>
#else
# include <fcntl.h>
# include <unistd.h>
#endif
bool gpt4all_fsync(int fd)
{
#if defined(Q_OS_WIN32)
HANDLE handle = HANDLE(_get_osfhandle(fd));
if (handle == INVALID_HANDLE_VALUE) {
errno = EBADF;
return false;
}
if (FlushFileBuffers(handle))
return true;
DWORD error = GetLastError();
switch (error) {
case ERROR_ACCESS_DENIED: // read-only file
return true;
case ERROR_INVALID_HANDLE: // not a regular file
errno = EINVAL;
default:
errno = EIO;
}
return false;
#elif defined(Q_OS_DARWIN)
return fcntl(fd, F_FULLFSYNC, 0) == 0;
#else
return fsync(fd) == 0;
#endif
}
bool gpt4all_fdatasync(int fd)
{
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
return gpt4all_fsync(fd);
#else
return fdatasync(fd) == 0;
#endif
}
bool gpt4all_syncdir(const QString &path)
{
#if defined(Q_OS_WIN32)
(void)path; // cannot sync a directory on Windows
return true;
#else
int fd = open(path.toLocal8Bit().constData(), O_RDONLY | O_DIRECTORY);
if (fd == -1) return false;
bool ok = gpt4all_fdatasync(fd);
close(fd);
return ok;
#endif
}

7
gpt4all-chat/oscompat.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
class QString;
bool gpt4all_fsync(int fd);
bool gpt4all_fdatasync(int fd);
bool gpt4all_syncdir(const QString &path);

View File

@ -1,101 +0,0 @@
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
import download
import network
import llm
MyDialog {
id: abpoutDialog
anchors.centerIn: parent
modal: false
padding: 20
width: 1024
height: column.height + 40
Theme {
id: theme
}
Column {
id: column
spacing: 20
Item {
width: childrenRect.width
height: childrenRect.height
Image {
id: img
anchors.top: parent.top
anchors.left: parent.left
width: 60
height: 60
source: "qrc:/gpt4all/icons/logo.svg"
}
Text {
anchors.left: img.right
anchors.leftMargin: 30
anchors.verticalCenter: img.verticalCenter
text: qsTr("About GPT4All")
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
font.bold: true
}
}
ScrollView {
clip: true
height: 200
width: 1024 - 40
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
MyTextArea {
id: welcome
width: 1024 - 40
textFormat: TextEdit.MarkdownText
text: qsTr("### Release notes\n")
+ Download.releaseInfo.notes
+ qsTr("### Contributors\n")
+ Download.releaseInfo.contributors
focus: false
readOnly: true
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Release notes")
Accessible.description: qsTr("Release notes for this version")
}
}
MySettingsLabel {
id: discordLink
width: parent.width
textFormat: Text.StyledText
wrapMode: Text.WordWrap
text: qsTr("Check out our discord channel <a href=\"https://discord.gg/4M2QFmTt2k\">https://discord.gg/4M2QFmTt2k</a>")
font.pixelSize: theme.fontSizeLarge
onLinkActivated: { Qt.openUrlExternally("https://discord.gg/4M2QFmTt2k") }
color: theme.textColor
linkColor: theme.linkColor
Accessible.role: Accessible.Link
Accessible.name: qsTr("Discord link")
}
MySettingsLabel {
id: nomicProps
width: parent.width
textFormat: Text.StyledText
wrapMode: Text.WordWrap
text: qsTr("Thank you to <a href=\"https://home.nomic.ai\">Nomic AI</a> and the community for contributing so much great data, code, ideas, and energy to the growing open source AI ecosystem!")
font.pixelSize: theme.fontSizeLarge
onLinkActivated: { Qt.openUrlExternally("https://home.nomic.ai") }
color: theme.textColor
linkColor: theme.linkColor
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Thank you blurb")
Accessible.description: qsTr("Contains embedded link to https://home.nomic.ai")
}
}
}

View File

@ -0,0 +1,170 @@
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
import QtQuick.Dialogs
import Qt.labs.folderlistmodel
import Qt5Compat.GraphicalEffects
import llm
import chatlistmodel
import download
import modellist
import network
import gpt4all
import mysettings
import localdocs
Rectangle {
id: addCollectionView
Theme {
id: theme
}
color: theme.viewBackground
signal localDocsViewRequested()
ColumnLayout {
id: mainArea
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.margins: 30
spacing: 50
RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: 50
MyButton {
id: backButton
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
text: qsTr("\u2190 Existing Collections")
borderWidth: 0
backgroundColor: theme.lighterButtonBackground
backgroundColorHovered: theme.lighterButtonBackgroundHovered
backgroundRadius: 5
padding: 15
topPadding: 8
bottomPadding: 8
textColor: theme.lighterButtonForeground
fontPixelSize: theme.fontSizeLarge
fontPixelBold: true
onClicked: {
localDocsViewRequested()
}
}
}
ColumnLayout {
id: root
Layout.alignment: Qt.AlignTop | Qt.AlignCenter
spacing: 50
property alias collection: collection.text
property alias folder_path: folderEdit.text
FolderDialog {
id: folderDialog
title: qsTr("Please choose a directory")
}
function openFolderDialog(currentFolder, onAccepted) {
folderDialog.currentFolder = currentFolder;
folderDialog.accepted.connect(function() { onAccepted(folderDialog.currentFolder); });
folderDialog.open();
}
Text {
horizontalAlignment: Qt.AlignHCenter
text: qsTr("New Local Doc\nCollection")
font.pixelSize: theme.fontSizeBanner
color: theme.titleTextColor
}
MyTextField {
id: collection
Layout.alignment: Qt.AlignCenter
Layout.minimumWidth: 400
horizontalAlignment: Text.AlignJustify
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
placeholderText: qsTr("Collection name...")
placeholderTextColor: theme.mutedTextColor
ToolTip.text: qsTr("Name of the collection to add (Required)")
ToolTip.visible: hovered
Accessible.role: Accessible.EditableText
Accessible.name: collection.text
Accessible.description: ToolTip.text
function showError() {
collection.placeholderTextColor = theme.textErrorColor
}
onTextChanged: {
collection.placeholderTextColor = theme.mutedTextColor
}
}
RowLayout {
Layout.alignment: Qt.AlignCenter
Layout.minimumWidth: 400
Layout.maximumWidth: 400
spacing: 10
MyDirectoryField {
id: folderEdit
Layout.fillWidth: true
text: root.folder_path
placeholderText: qsTr("Folder path...")
font.pixelSize: theme.fontSizeLarge
placeholderTextColor: theme.mutedTextColor
ToolTip.text: qsTr("Folder path to documents (Required)")
ToolTip.visible: hovered
function showError() {
folderEdit.placeholderTextColor = theme.textErrorColor
}
onTextChanged: {
folderEdit.placeholderTextColor = theme.mutedTextColor
}
}
MySettingsButton {
id: browseButton
text: qsTr("Browse")
onClicked: {
root.openFolderDialog(StandardPaths.writableLocation(StandardPaths.HomeLocation), function(selectedFolder) {
root.folder_path = selectedFolder
})
}
}
}
MyButton {
Layout.alignment: Qt.AlignCenter
Layout.minimumWidth: 400
text: qsTr("Create Collection")
onClicked: {
var isError = false;
if (root.collection === "") {
isError = true;
collection.showError();
}
if (root.folder_path === "" || !folderEdit.isValid) {
isError = true;
folderEdit.showError();
}
if (isError)
return;
LocalDocs.addFolder(root.collection, root.folder_path)
root.collection = ""
root.folder_path = ""
collection.clear()
localDocsViewRequested()
}
}
}
}
}

View File

@ -0,0 +1,726 @@
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
import QtQuick.Dialogs
import Qt.labs.folderlistmodel
import Qt5Compat.GraphicalEffects
import llm
import chatlistmodel
import download
import modellist
import network
import gpt4all
import mysettings
import localdocs
Rectangle {
id: addModelView
Theme {
id: theme
}
color: theme.viewBackground
signal modelsViewRequested()
PopupDialog {
id: downloadingErrorPopup
anchors.centerIn: parent
shouldTimeOut: false
}
ColumnLayout {
id: mainArea
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.margins: 30
spacing: 50
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: 50
MyButton {
id: backButton
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
text: qsTr("\u2190 Existing Models")
borderWidth: 0
backgroundColor: theme.lighterButtonBackground
backgroundColorHovered: theme.lighterButtonBackgroundHovered
backgroundRadius: 5
padding: 15
topPadding: 8
bottomPadding: 8
textColor: theme.lighterButtonForeground
fontPixelSize: theme.fontSizeLarge
fontPixelBold: true
onClicked: {
modelsViewRequested()
}
}
Text {
id: welcome
text: qsTr("Explore Models")
font.pixelSize: theme.fontSizeBanner
color: theme.titleTextColor
}
RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignCenter
Layout.margins: 0
spacing: 10
MyTextField {
id: discoverField
property string textBeingSearched: ""
readOnly: ModelList.discoverInProgress
Layout.alignment: Qt.AlignCenter
Layout.fillWidth: true
Layout.preferredHeight: 90
font.pixelSize: theme.fontSizeLarger
placeholderText: qsTr("Discover and download models by keyword search...")
Accessible.role: Accessible.EditableText
Accessible.name: placeholderText
Accessible.description: qsTr("Text field for discovering and filtering downloadable models")
Connections {
target: ModelList
function onDiscoverInProgressChanged() {
if (ModelList.discoverInProgress) {
discoverField.textBeingSearched = discoverField.text;
discoverField.text = qsTr("Searching \u00B7 ") + discoverField.textBeingSearched;
} else {
discoverField.text = discoverField.textBeingSearched;
discoverField.textBeingSearched = "";
}
}
}
background: ProgressBar {
id: discoverProgressBar
indeterminate: ModelList.discoverInProgress && ModelList.discoverProgress === 0.0
value: ModelList.discoverProgress
background: Rectangle {
color: theme.controlBackground
border.color: theme.controlBorder
radius: 10
}
contentItem: Item {
Rectangle {
visible: ModelList.discoverInProgress
anchors.bottom: parent.bottom
width: discoverProgressBar.visualPosition * parent.width
height: 10
radius: 2
color: theme.progressForeground
}
}
}
Keys.onReturnPressed: (event)=> {
if (event.modifiers & Qt.ControlModifier || event.modifiers & Qt.ShiftModifier)
event.accepted = false;
else {
editingFinished();
sendDiscovery()
}
}
function sendDiscovery() {
ModelList.downloadableModels.discoverAndFilter(discoverField.text);
}
RowLayout {
spacing: 0
anchors.right: discoverField.right
anchors.verticalCenter: discoverField.verticalCenter
anchors.rightMargin: 15
visible: !ModelList.discoverInProgress
MyMiniButton {
id: clearDiscoverButton
backgroundColor: theme.textColor
backgroundColorHovered: theme.iconBackgroundDark
visible: discoverField.text !== ""
contentItem: Text {
color: clearDiscoverButton.hovered ? theme.iconBackgroundDark : theme.textColor
text: "\u2715"
font.pixelSize: theme.fontSizeLarge
}
onClicked: {
discoverField.text = ""
discoverField.sendDiscovery() // should clear results
}
}
MyMiniButton {
backgroundColor: theme.textColor
backgroundColorHovered: theme.iconBackgroundDark
source: "qrc:/gpt4all/icons/settings.svg"
onClicked: {
discoveryTools.visible = !discoveryTools.visible
}
}
MyMiniButton {
id: sendButton
enabled: !ModelList.discoverInProgress
backgroundColor: theme.textColor
backgroundColorHovered: theme.iconBackgroundDark
source: "qrc:/gpt4all/icons/send_message.svg"
Accessible.name: qsTr("Initiate model discovery and filtering")
Accessible.description: qsTr("Triggers discovery and filtering of models")
onClicked: {
discoverField.sendDiscovery()
}
}
}
}
}
RowLayout {
id: discoveryTools
Layout.fillWidth: true
Layout.alignment: Qt.AlignCenter
Layout.margins: 0
spacing: 20
visible: false
MyComboBox {
id: comboSort
model: [qsTr("Default"), qsTr("Likes"), qsTr("Downloads"), qsTr("Recent")]
currentIndex: ModelList.discoverSort
contentItem: Text {
anchors.horizontalCenter: parent.horizontalCenter
rightPadding: 30
color: theme.textColor
text: {
return qsTr("Sort by: ") + comboSort.displayText
}
font.pixelSize: theme.fontSizeLarger
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
onActivated: function (index) {
ModelList.discoverSort = index;
}
}
MyComboBox {
id: comboSortDirection
model: [qsTr("Asc"), qsTr("Desc")]
currentIndex: {
if (ModelList.discoverSortDirection === 1)
return 0
else
return 1;
}
contentItem: Text {
anchors.horizontalCenter: parent.horizontalCenter
rightPadding: 30
color: theme.textColor
text: {
return qsTr("Sort dir: ") + comboSortDirection.displayText
}
font.pixelSize: theme.fontSizeLarger
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
onActivated: function (index) {
if (index === 0)
ModelList.discoverSortDirection = 1;
else
ModelList.discoverSortDirection = -1;
}
}
MyComboBox {
id: comboLimit
model: ["5", "10", "20", "50", "100", qsTr("None")]
currentIndex: {
if (ModelList.discoverLimit === 5)
return 0;
else if (ModelList.discoverLimit === 10)
return 1;
else if (ModelList.discoverLimit === 20)
return 2;
else if (ModelList.discoverLimit === 50)
return 3;
else if (ModelList.discoverLimit === 100)
return 4;
else if (ModelList.discoverLimit === -1)
return 5;
}
contentItem: Text {
anchors.horizontalCenter: parent.horizontalCenter
rightPadding: 30
color: theme.textColor
text: {
return qsTr("Limit: ") + comboLimit.displayText
}
font.pixelSize: theme.fontSizeLarger
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
onActivated: function (index) {
switch (index) {
case 0:
ModelList.discoverLimit = 5; break;
case 1:
ModelList.discoverLimit = 10; break;
case 2:
ModelList.discoverLimit = 20; break;
case 3:
ModelList.discoverLimit = 50; break;
case 4:
ModelList.discoverLimit = 100; break;
case 5:
ModelList.discoverLimit = -1; break;
}
}
}
}
}
Label {
visible: !ModelList.downloadableModels.count && !ModelList.asyncModelRequestOngoing
Layout.fillWidth: true
Layout.fillHeight: true
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
text: qsTr("Network error: could not retrieve http://gpt4all.io/models/models3.json")
font.pixelSize: theme.fontSizeLarge
color: theme.mutedTextColor
}
MyBusyIndicator {
visible: !ModelList.downloadableModels.count && ModelList.asyncModelRequestOngoing
running: ModelList.asyncModelRequestOngoing
Accessible.role: Accessible.Animation
Layout.alignment: Qt.AlignCenter
Accessible.name: qsTr("Busy indicator")
Accessible.description: qsTr("Displayed when the models request is ongoing")
}
ScrollView {
id: scrollView
ScrollBar.vertical.policy: ScrollBar.AsNeeded
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
ListView {
id: modelListView
model: ModelList.downloadableModels
boundsBehavior: Flickable.StopAtBounds
spacing: 30
delegate: Rectangle {
id: delegateItem
width: modelListView.width
height: childrenRect.height + 60
color: theme.conversationBackground
radius: 10
border.width: 1
border.color: theme.controlBorder
ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 30
Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
text: name
elide: Text.ElideRight
color: theme.titleTextColor
font.pixelSize: theme.fontSizeLargest
font.bold: true
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Model file")
Accessible.description: qsTr("Model file to be downloaded")
}
Rectangle {
Layout.fillWidth: true
height: 1
color: theme.dividerColor
}
RowLayout {
Layout.topMargin: 10
Layout.fillWidth: true
Text {
id: descriptionText
text: description
font.pixelSize: theme.fontSizeLarge
Layout.fillWidth: true
wrapMode: Text.WordWrap
textFormat: Text.StyledText
color: theme.textColor
linkColor: theme.textColor
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Description")
Accessible.description: qsTr("File description")
onLinkActivated: Qt.openUrlExternally(link)
}
// FIXME Need to overhaul design here which must take into account
// features not present in current figma including:
// * Ability to cancel a current download
// * Ability to resume a download
// * The presentation of an error if encountered
// * Whether to show already installed models
// * Install of remote models with API keys
// * The presentation of the progress bar
Rectangle {
id: actionBox
width: childrenRect.width + 20
color: "transparent"
border.width: 1
border.color: theme.dividerColor
radius: 10
Layout.rightMargin: 20
Layout.bottomMargin: 20
Layout.minimumHeight: childrenRect.height + 20
Layout.alignment: Qt.AlignRight | Qt.AlignTop
ColumnLayout {
spacing: 0
MySettingsButton {
id: downloadButton
text: isDownloading ? qsTr("Cancel") : isIncomplete ? qsTr("Resume") : qsTr("Download")
font.pixelSize: theme.fontSizeLarge
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
visible: !isOnline && !installed && !calcHash && downloadError === ""
Accessible.description: qsTr("Stop/restart/start the download")
onClicked: {
if (!isDownloading) {
Download.downloadModel(filename);
} else {
Download.cancelDownload(filename);
}
}
}
MySettingsDestructiveButton {
id: removeButton
text: qsTr("Remove")
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
visible: installed || downloadError !== ""
Accessible.description: qsTr("Remove model from filesystem")
onClicked: {
Download.removeModel(filename);
}
}
MySettingsButton {
id: installButton
visible: !installed && isOnline
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
text: qsTr("Install")
font.pixelSize: theme.fontSizeLarge
onClicked: {
if (apiKey.text === "")
apiKey.showError();
else
Download.installModel(filename, apiKey.text);
}
Accessible.role: Accessible.Button
Accessible.name: qsTr("Install")
Accessible.description: qsTr("Install online model")
}
ColumnLayout {
spacing: 0
Label {
Layout.topMargin: 20
Layout.leftMargin: 20
visible: downloadError !== ""
textFormat: Text.StyledText
text: "<strong><font size=\"1\">"
+ qsTr("<a href=\"#error\">Error</a>")
+ "</strong></font>"
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
linkColor: theme.textErrorColor
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: qsTr("Describes an error that occurred when downloading")
onLinkActivated: {
downloadingErrorPopup.text = downloadError;
downloadingErrorPopup.open();
}
}
Label {
visible: LLM.systemTotalRAMInGB() < ramrequired
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.maximumWidth: 300
textFormat: Text.StyledText
text: qsTr("<strong><font size=\"2\">WARNING: Not recommended for your hardware.")
+ qsTr(" Model requires more memory (") + ramrequired
+ qsTr(" GB) than your system has available (")
+ LLM.systemTotalRAMInGBString() + ").</strong></font>"
color: theme.textErrorColor
font.pixelSize: theme.fontSizeLarge
wrapMode: Text.WordWrap
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: qsTr("Error for incompatible hardware")
onLinkActivated: {
downloadingErrorPopup.text = downloadError;
downloadingErrorPopup.open();
}
}
}
ColumnLayout {
visible: isDownloading && !calcHash
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
spacing: 20
ProgressBar {
id: itemProgressBar
Layout.fillWidth: true
width: 200
value: bytesReceived / bytesTotal
background: Rectangle {
implicitHeight: 45
color: theme.progressBackground
radius: 3
}
contentItem: Item {
implicitHeight: 40
Rectangle {
width: itemProgressBar.visualPosition * parent.width
height: parent.height
radius: 2
color: theme.progressForeground
}
}
Accessible.role: Accessible.ProgressBar
Accessible.name: qsTr("Download progressBar")
Accessible.description: qsTr("Shows the progress made in the download")
}
Label {
id: speedLabel
color: theme.textColor
Layout.alignment: Qt.AlignRight
text: speed
font.pixelSize: theme.fontSizeLarge
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Download speed")
Accessible.description: qsTr("Download speed in bytes/kilobytes/megabytes per second")
}
}
RowLayout {
visible: calcHash
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.maximumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
clip: true
Label {
id: calcHashLabel
color: theme.textColor
text: qsTr("Calculating...")
font.pixelSize: theme.fontSizeLarge
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: qsTr("Whether the file hash is being calculated")
}
MyBusyIndicator {
id: busyCalcHash
running: calcHash
Accessible.role: Accessible.Animation
Accessible.name: qsTr("Busy indicator")
Accessible.description: qsTr("Displayed when the file hash is being calculated")
}
}
MyTextField {
id: apiKey
visible: !installed && isOnline
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
wrapMode: Text.WrapAnywhere
function showError() {
apiKey.placeholderTextColor = theme.textErrorColor
}
onTextChanged: {
apiKey.placeholderTextColor = theme.mutedTextColor
}
placeholderText: qsTr("enter $API_KEY")
Accessible.role: Accessible.EditableText
Accessible.name: placeholderText
Accessible.description: qsTr("Whether the file hash is being calculated")
}
}
}
}
Item {
Layout.minimumWidth: childrenRect.width
Layout.minimumHeight: childrenRect.height
Layout.bottomMargin: 10
RowLayout {
id: paramRow
anchors.centerIn: parent
ColumnLayout {
Layout.topMargin: 10
Layout.bottomMargin: 10
Layout.leftMargin: 20
Layout.rightMargin: 20
Text {
text: qsTr("File size")
font.pixelSize: theme.fontSizeSmaller
color: theme.mutedDarkTextColor
}
Text {
text: filesize
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.bold: true
}
}
Rectangle {
width: 1
Layout.fillHeight: true
color: theme.dividerColor
}
ColumnLayout {
Layout.topMargin: 10
Layout.bottomMargin: 10
Layout.leftMargin: 20
Layout.rightMargin: 20
Text {
text: qsTr("RAM required")
font.pixelSize: theme.fontSizeSmaller
color: theme.mutedDarkTextColor
}
Text {
text: ramrequired + qsTr(" GB")
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.bold: true
}
}
Rectangle {
width: 1
Layout.fillHeight: true
color: theme.dividerColor
}
ColumnLayout {
Layout.topMargin: 10
Layout.bottomMargin: 10
Layout.leftMargin: 20
Layout.rightMargin: 20
Text {
text: qsTr("Parameters")
font.pixelSize: theme.fontSizeSmaller
color: theme.mutedDarkTextColor
}
Text {
text: parameters
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.bold: true
}
}
Rectangle {
width: 1
Layout.fillHeight: true
color: theme.dividerColor
}
ColumnLayout {
Layout.topMargin: 10
Layout.bottomMargin: 10
Layout.leftMargin: 20
Layout.rightMargin: 20
Text {
text: qsTr("Quant")
font.pixelSize: theme.fontSizeSmaller
color: theme.mutedDarkTextColor
}
Text {
text: quant
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.bold: true
}
}
Rectangle {
width: 1
Layout.fillHeight: true
color: theme.dividerColor
}
ColumnLayout {
Layout.topMargin: 10
Layout.bottomMargin: 10
Layout.leftMargin: 20
Layout.rightMargin: 20
Text {
text: qsTr("Type")
font.pixelSize: theme.fontSizeSmaller
color: theme.mutedDarkTextColor
}
Text {
text: type
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.bold: true
}
}
}
Rectangle {
color: "transparent"
anchors.fill: paramRow
border.color: theme.dividerColor
border.width: 1
radius: 10
}
}
Rectangle {
Layout.fillWidth: true
height: 1
color: theme.dividerColor
}
}
}
}
}
}
}

View File

@ -7,31 +7,97 @@ import QtQuick.Dialogs
import modellist import modellist
import mysettings import mysettings
import network import network
import llm
MySettingsTab { MySettingsTab {
onRestoreDefaultsClicked: { onRestoreDefaultsClicked: {
MySettings.restoreApplicationDefaults(); MySettings.restoreApplicationDefaults();
} }
title: qsTr("Application") title: qsTr("Application")
NetworkDialog {
id: networkDialog
anchors.centerIn: parent
width: Math.min(1024, window.width - (window.width * .2))
height: Math.min(600, window.height - (window.height * .2))
Item {
Accessible.role: Accessible.Dialog
Accessible.name: qsTr("Network dialog")
Accessible.description: qsTr("opt-in to share feedback/conversations")
}
}
Dialog {
id: checkForUpdatesError
anchors.centerIn: parent
modal: false
padding: 20
Text {
horizontalAlignment: Text.AlignJustify
text: qsTr("ERROR: Update system could not find the MaintenanceTool used<br>
to check for updates!<br><br>
Did you install this application using the online installer? If so,<br>
the MaintenanceTool executable should be located one directory<br>
above where this application resides on your filesystem.<br><br>
If you can't start it manually, then I'm afraid you'll have to<br>
reinstall.")
color: theme.textErrorColor
font.pixelSize: theme.fontSizeLarge
Accessible.role: Accessible.Dialog
Accessible.name: text
Accessible.description: qsTr("Error dialog")
}
background: Rectangle {
anchors.fill: parent
color: theme.containerBackground
border.width: 1
border.color: theme.dialogBorder
radius: 10
}
}
contentItem: GridLayout { contentItem: GridLayout {
id: applicationSettingsTabInner id: applicationSettingsTabInner
columns: 3 columns: 3
rowSpacing: 10 rowSpacing: 30
columnSpacing: 10 columnSpacing: 10
ColumnLayout {
Layout.row: 0
Layout.column: 0
Layout.columnSpan: 3
Layout.fillWidth: true
spacing: 10
Label {
color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: "General"
}
Rectangle {
Layout.fillWidth: true
height: 2
color: theme.settingsDivider
}
}
MySettingsLabel { MySettingsLabel {
id: themeLabel id: themeLabel
text: qsTr("Theme") text: qsTr("Theme")
helpText: qsTr("Customize the colors of GPT4All")
Layout.row: 1 Layout.row: 1
Layout.column: 0 Layout.column: 0
} }
MyComboBox { MyComboBox {
id: themeBox id: themeBox
Layout.row: 1 Layout.row: 1
Layout.column: 1 Layout.column: 2
Layout.columnSpan: 1
Layout.minimumWidth: 200 Layout.minimumWidth: 200
Layout.maximumWidth: 200
Layout.fillWidth: false Layout.fillWidth: false
model: ["Dark", "Light", "LegacyDark"] Layout.alignment: Qt.AlignRight
model: [qsTr("Dark"), qsTr("Light"), qsTr("LegacyDark")]
Accessible.role: Accessible.ComboBox Accessible.role: Accessible.ComboBox
Accessible.name: qsTr("Color theme") Accessible.name: qsTr("Color theme")
Accessible.description: qsTr("Color theme for the chat client to use") Accessible.description: qsTr("Color theme for the chat client to use")
@ -54,16 +120,18 @@ MySettingsTab {
MySettingsLabel { MySettingsLabel {
id: fontLabel id: fontLabel
text: qsTr("Font Size") text: qsTr("Font Size")
helpText: qsTr("How big your font is displayed")
Layout.row: 2 Layout.row: 2
Layout.column: 0 Layout.column: 0
} }
MyComboBox { MyComboBox {
id: fontBox id: fontBox
Layout.row: 2 Layout.row: 2
Layout.column: 1 Layout.column: 2
Layout.columnSpan: 1 Layout.minimumWidth: 200
Layout.minimumWidth: 100 Layout.maximumWidth: 200
Layout.fillWidth: false Layout.fillWidth: false
Layout.alignment: Qt.AlignRight
model: ["Small", "Medium", "Large"] model: ["Small", "Medium", "Large"]
Accessible.role: Accessible.ComboBox Accessible.role: Accessible.ComboBox
Accessible.name: qsTr("Font size") Accessible.name: qsTr("Font size")
@ -87,16 +155,18 @@ MySettingsTab {
MySettingsLabel { MySettingsLabel {
id: deviceLabel id: deviceLabel
text: qsTr("Device") text: qsTr("Device")
helpText: qsTr("The hardware device used to load the model")
Layout.row: 3 Layout.row: 3
Layout.column: 0 Layout.column: 0
} }
MyComboBox { MyComboBox {
id: deviceBox id: deviceBox
Layout.row: 3 Layout.row: 3
Layout.column: 1 Layout.column: 2
Layout.columnSpan: 1 Layout.minimumWidth: 400
Layout.minimumWidth: 350 Layout.maximumWidth: 400
Layout.fillWidth: false Layout.fillWidth: false
Layout.alignment: Qt.AlignRight
model: MySettings.deviceList model: MySettings.deviceList
Accessible.role: Accessible.ComboBox Accessible.role: Accessible.ComboBox
Accessible.name: qsTr("Device") Accessible.name: qsTr("Device")
@ -123,16 +193,17 @@ MySettingsTab {
MySettingsLabel { MySettingsLabel {
id: defaultModelLabel id: defaultModelLabel
text: qsTr("Default model") text: qsTr("Default model")
helpText: qsTr("The preferred default model")
Layout.row: 4 Layout.row: 4
Layout.column: 0 Layout.column: 0
} }
MyComboBox { MyComboBox {
id: comboBox id: comboBox
Layout.row: 4 Layout.row: 4
Layout.column: 1 Layout.column: 2
Layout.columnSpan: 2 Layout.minimumWidth: 400
Layout.minimumWidth: 350 Layout.maximumWidth: 400
Layout.fillWidth: true Layout.alignment: Qt.AlignRight
model: ModelList.userDefaultModelList model: ModelList.userDefaultModelList
Accessible.role: Accessible.ComboBox Accessible.role: Accessible.ComboBox
Accessible.name: qsTr("Default model") Accessible.name: qsTr("Default model")
@ -156,45 +227,96 @@ MySettingsTab {
MySettingsLabel { MySettingsLabel {
id: modelPathLabel id: modelPathLabel
text: qsTr("Download path") text: qsTr("Download path")
helpText: qsTr("The download folder for models")
Layout.row: 5 Layout.row: 5
Layout.column: 0 Layout.column: 0
} }
MyDirectoryField {
id: modelPathDisplayField RowLayout {
text: MySettings.modelPath
font.pixelSize: theme.fontSizeLarge
implicitWidth: 300
Layout.row: 5 Layout.row: 5
Layout.column: 1 Layout.column: 2
Layout.fillWidth: true Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("Path where model files will be downloaded to") Layout.minimumWidth: 400
ToolTip.visible: hovered Layout.maximumWidth: 400
Accessible.role: Accessible.ToolTip spacing: 10
Accessible.name: modelPathDisplayField.text MyDirectoryField {
Accessible.description: ToolTip.text id: modelPathDisplayField
onEditingFinished: { text: MySettings.modelPath
if (isValid) { font.pixelSize: theme.fontSizeLarge
MySettings.modelPath = modelPathDisplayField.text implicitWidth: 300
} else { Layout.fillWidth: true
text = MySettings.modelPath ToolTip.text: qsTr("Path where model files will be downloaded to")
ToolTip.visible: hovered
Accessible.role: Accessible.ToolTip
Accessible.name: modelPathDisplayField.text
Accessible.description: ToolTip.text
onEditingFinished: {
if (isValid) {
MySettings.modelPath = modelPathDisplayField.text
} else {
text = MySettings.modelPath
}
}
}
MySettingsButton {
text: qsTr("Browse")
Accessible.description: qsTr("Choose where to save model files")
onClicked: {
openFolderDialog("file://" + MySettings.modelPath, function(selectedFolder) {
MySettings.modelPath = selectedFolder
})
} }
} }
} }
MySettingsButton {
Layout.row: 5 MySettingsLabel {
id: dataLakeLabel
text: qsTr("Opensource Datalake")
helpText: qsTr("Send your data to the GPT4All Open Source Datalake.")
Layout.row: 6
Layout.column: 0
}
MyCheckBox {
id: dataLakeBox
Layout.row: 6
Layout.column: 2 Layout.column: 2
text: qsTr("Browse") Layout.alignment: Qt.AlignRight
Accessible.description: qsTr("Choose where to save model files") checked: MySettings.networkIsActive
onClicked: { onClicked: {
openFolderDialog("file://" + MySettings.modelPath, function(selectedFolder) { if (MySettings.networkIsActive) {
MySettings.modelPath = selectedFolder MySettings.networkIsActive = false
}) } else
networkDialog.open()
}
ToolTip.text: qsTr("Reveals a dialogue where you can opt-in for sharing data over network")
ToolTip.visible: hovered
}
ColumnLayout {
Layout.row: 7
Layout.column: 0
Layout.columnSpan: 3
Layout.fillWidth: true
spacing: 10
Label {
color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: "Advanced"
}
Rectangle {
Layout.fillWidth: true
height: 2
color: theme.settingsDivider
} }
} }
MySettingsLabel { MySettingsLabel {
id: nThreadsLabel id: nThreadsLabel
text: qsTr("CPU Threads") text: qsTr("CPU Threads")
Layout.row: 6 helpText: qsTr("Number of CPU threads for inference and embedding")
Layout.row: 8
Layout.column: 0 Layout.column: 0
} }
MyTextField { MyTextField {
@ -203,8 +325,11 @@ MySettingsTab {
font.pixelSize: theme.fontSizeLarge font.pixelSize: theme.fontSizeLarge
ToolTip.text: qsTr("Amount of processing threads to use bounded by 1 and number of logical processors") ToolTip.text: qsTr("Amount of processing threads to use bounded by 1 and number of logical processors")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.row: 6 Layout.alignment: Qt.AlignRight
Layout.column: 1 Layout.row: 8
Layout.column: 2
Layout.minimumWidth: 200
Layout.maximumWidth: 200
validator: IntValidator { validator: IntValidator {
bottom: 1 bottom: 1
} }
@ -223,14 +348,16 @@ MySettingsTab {
} }
MySettingsLabel { MySettingsLabel {
id: saveChatsContextLabel id: saveChatsContextLabel
text: qsTr("Save chats context to disk") text: qsTr("Save chat context")
Layout.row: 7 helpText: qsTr("Save chat context to disk")
Layout.row: 9
Layout.column: 0 Layout.column: 0
} }
MyCheckBox { MyCheckBox {
id: saveChatsContextBox id: saveChatsContextBox
Layout.row: 7 Layout.row: 9
Layout.column: 1 Layout.column: 2
Layout.alignment: Qt.AlignRight
checked: MySettings.saveChatsContext checked: MySettings.saveChatsContext
onClicked: { onClicked: {
MySettings.saveChatsContext = !MySettings.saveChatsContext MySettings.saveChatsContext = !MySettings.saveChatsContext
@ -241,13 +368,15 @@ MySettingsTab {
MySettingsLabel { MySettingsLabel {
id: serverChatLabel id: serverChatLabel
text: qsTr("Enable API server") text: qsTr("Enable API server")
Layout.row: 8 helpText: qsTr("A local http server running on local port")
Layout.row: 10
Layout.column: 0 Layout.column: 0
} }
MyCheckBox { MyCheckBox {
id: serverChatBox id: serverChatBox
Layout.row: 8 Layout.row: 10
Layout.column: 1 Layout.column: 2
Layout.alignment: Qt.AlignRight
checked: MySettings.serverChat checked: MySettings.serverChat
onClicked: { onClicked: {
MySettings.serverChat = !MySettings.serverChat MySettings.serverChat = !MySettings.serverChat
@ -257,8 +386,9 @@ MySettingsTab {
} }
MySettingsLabel { MySettingsLabel {
id: serverPortLabel id: serverPortLabel
text: qsTr("API Server Port (Requires restart):") text: qsTr("API Server Port:")
Layout.row: 9 helpText: qsTr("A local port to run the server (Requires restart")
Layout.row: 11
Layout.column: 0 Layout.column: 0
} }
MyTextField { MyTextField {
@ -268,8 +398,11 @@ MySettingsTab {
font.pixelSize: theme.fontSizeLarge font.pixelSize: theme.fontSizeLarge
ToolTip.text: qsTr("Api server port. WARNING: You need to restart the application for it to take effect") ToolTip.text: qsTr("Api server port. WARNING: You need to restart the application for it to take effect")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.row: 9 Layout.row: 11
Layout.column: 1 Layout.column: 2
Layout.minimumWidth: 200
Layout.maximumWidth: 200
Layout.alignment: Qt.AlignRight
validator: IntValidator { validator: IntValidator {
bottom: 1 bottom: 1
} }
@ -286,58 +419,53 @@ MySettingsTab {
Accessible.name: serverPortField.text Accessible.name: serverPortField.text
Accessible.description: ToolTip.text Accessible.description: ToolTip.text
} }
Rectangle {
Layout.row: 10
Layout.column: 0
Layout.columnSpan: 3
Layout.fillWidth: true
height: 3
color: theme.accentColor
}
}
advancedSettings: GridLayout {
columns: 3
rowSpacing: 10
columnSpacing: 10
Rectangle {
Layout.row: 2
Layout.column: 0
Layout.fillWidth: true
Layout.columnSpan: 3
height: 3
color: theme.accentColor
}
MySettingsLabel { MySettingsLabel {
id: gpuOverrideLabel id: gpuOverrideLabel
text: qsTr("Force Metal (macOS+arm)") text: qsTr("Force Metal (macOS+arm)")
Layout.row: 1 Layout.row: 13
Layout.column: 0 Layout.column: 0
} }
RowLayout { MyCheckBox {
Layout.row: 1 id: gpuOverrideBox
Layout.column: 1 Layout.row: 13
Layout.columnSpan: 2 Layout.column: 2
MyCheckBox { Layout.alignment: Qt.AlignRight
id: gpuOverrideBox checked: MySettings.forceMetal
checked: MySettings.forceMetal onClicked: {
onClicked: { MySettings.forceMetal = !MySettings.forceMetal
MySettings.forceMetal = !MySettings.forceMetal
}
} }
ToolTip.text: qsTr("WARNING: On macOS with arm (M1+) this setting forces usage of the GPU. Can cause crashes if the model requires more RAM than the system supports. Because of crash possibility the setting will not persist across restarts of the application. This has no effect on non-macs or intel.")
ToolTip.visible: hovered
}
Item { MySettingsLabel {
Layout.fillWidth: true id: updatesLabel
Layout.alignment: Qt.AlignTop text: qsTr("Check for updates")
Layout.minimumHeight: warningLabel.height helpText: qsTr("Click to see if an update to the application is available");
MySettingsLabel { Layout.row: 14
id: warningLabel Layout.column: 0
width: parent.width }
color: theme.textErrorColor
wrapMode: Text.WordWrap MySettingsButton {
text: qsTr("WARNING: On macOS with arm (M1+) this setting forces usage of the GPU. Can cause crashes if the model requires more RAM than the system supports. Because of crash possibility the setting will not persist across restarts of the application. This has no effect on non-macs or intel.") Layout.row: 14
} Layout.column: 2
Layout.alignment: Qt.AlignRight
text: qsTr("Updates");
onClicked: {
if (!LLM.checkForUpdates())
checkForUpdatesError.open()
} }
} }
Rectangle {
Layout.row: 15
Layout.column: 0
Layout.columnSpan: 3
Layout.fillWidth: true
height: 2
color: theme.settingsDivider
}
} }
} }

View File

@ -16,23 +16,33 @@ Rectangle {
id: theme id: theme
} }
signal downloadClicked color: theme.viewBackground
signal aboutClicked
color: theme.containerBackground Rectangle {
id: borderRight
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
width: 2
color: theme.dividerColor
}
Item { Item {
anchors.fill: parent anchors.top: parent.top
anchors.margins: 10 anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: borderRight.left
Accessible.role: Accessible.Pane Accessible.role: Accessible.Pane
Accessible.name: qsTr("Drawer") Accessible.name: qsTr("Drawer")
Accessible.description: qsTr("Main navigation drawer") Accessible.description: qsTr("Main navigation drawer")
MyButton { MySettingsButton {
id: newChat id: newChat
anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.margins: 20
font.pixelSize: theme.fontSizeLarger font.pixelSize: theme.fontSizeLarger
topPadding: 20 topPadding: 20
bottomPadding: 20 bottomPadding: 20
@ -45,20 +55,31 @@ Rectangle {
} }
} }
Rectangle {
id: divider
anchors.top: newChat.bottom
anchors.margins: 20
anchors.topMargin: 15
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: theme.dividerColor
}
ScrollView { ScrollView {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: -10 anchors.topMargin: 15
anchors.topMargin: 10 anchors.top: divider.bottom
anchors.top: newChat.bottom anchors.bottom: parent.bottom
anchors.bottom: checkForUpdatesButton.top anchors.bottomMargin: 15
anchors.bottomMargin: 10
ScrollBar.vertical.policy: ScrollBar.AlwaysOff ScrollBar.vertical.policy: ScrollBar.AlwaysOff
clip: true clip: true
ListView { ListView {
id: conversationList id: conversationList
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10 anchors.rightMargin: 10
model: ChatListModel model: ChatListModel
@ -71,6 +92,33 @@ Rectangle {
anchors.bottom: conversationList.bottom anchors.bottom: conversationList.bottom
} }
Component {
id: sectionHeading
Rectangle {
width: ListView.view.width
height: childrenRect.height
color: "transparent"
property bool isServer: ChatListModel.get(parent.index) && ChatListModel.get(parent.index).isServer
visible: !isServer || MySettings.serverChat
required property string section
Text {
leftPadding: 10
rightPadding: 10
topPadding: 15
bottomPadding: 5
text: parent.section
color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarge
}
}
}
section.property: "section"
section.criteria: ViewSection.FullString
section.delegate: sectionHeading
delegate: Rectangle { delegate: Rectangle {
id: chatRectangle id: chatRectangle
width: conversationList.width width: conversationList.width
@ -80,21 +128,25 @@ Rectangle {
property bool trashQuestionDisplayed: false property bool trashQuestionDisplayed: false
visible: !isServer || MySettings.serverChat visible: !isServer || MySettings.serverChat
z: isCurrent ? 199 : 1 z: isCurrent ? 199 : 1
color: index % 2 === 0 ? theme.darkContrast : theme.lightContrast color: isCurrent ? theme.selectedBackground : "transparent"
border.width: isCurrent border.width: isCurrent
border.color: chatName.readOnly ? theme.assistantColor : theme.userColor border.color: theme.dividerColor
radius: 10
TextField { TextField {
id: chatName id: chatName
anchors.left: parent.left anchors.left: parent.left
anchors.right: buttons.left anchors.right: buttons.left
color: theme.textColor color: theme.styledTextColor
padding: 15 topPadding: 15
bottomPadding: 15
focus: false focus: false
readOnly: true readOnly: true
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
hoverEnabled: false // Disable hover events on the TextArea hoverEnabled: false // Disable hover events on the TextArea
selectByMouse: false // Disable text selection in the TextArea selectByMouse: false // Disable text selection in the TextArea
font.pixelSize: theme.fontSizeLarge font.pixelSize: theme.fontSizeLarge
font.bold: true
text: readOnly ? metrics.elidedText : name text: readOnly ? metrics.elidedText : name
horizontalAlignment: TextInput.AlignLeft horizontalAlignment: TextInput.AlignLeft
opacity: trashQuestionDisplayed ? 0.5 : 1.0 opacity: trashQuestionDisplayed ? 0.5 : 1.0
@ -103,7 +155,7 @@ Rectangle {
font: chatName.font font: chatName.font
text: name text: name
elide: Text.ElideRight elide: Text.ElideRight
elideWidth: chatName.width - 40 elideWidth: chatName.width - 15
} }
background: Rectangle { background: Rectangle {
color: "transparent" color: "transparent"
@ -240,45 +292,5 @@ Rectangle {
Accessible.description: qsTr("List of chats in the drawer dialog") Accessible.description: qsTr("List of chats in the drawer dialog")
} }
} }
MyButton {
id: checkForUpdatesButton
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: downloadButton.top
anchors.bottomMargin: 10
text: qsTr("Updates")
font.pixelSize: theme.fontSizeLarge
Accessible.description: qsTr("Launch an external application that will check for updates to the installer")
onClicked: {
if (!LLM.checkForUpdates())
checkForUpdatesError.open()
}
}
MyButton {
id: downloadButton
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: aboutButton.top
anchors.bottomMargin: 10
text: qsTr("Downloads")
Accessible.description: qsTr("Launch a dialog to download new models")
onClicked: {
downloadClicked()
}
}
MyButton {
id: aboutButton
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
text: qsTr("About")
Accessible.description: qsTr("Launch a dialog to show the about page")
onClicked: {
aboutClicked()
}
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,148 +0,0 @@
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
import QtQuick.Dialogs
import chatlistmodel
import localdocs
import llm
MyDialog {
id: collectionsDialog
modal: true
padding: 20
width: 480
height: 640
signal addRemoveClicked
property var currentChat: ChatListModel.currentChat
Label {
id: listLabel
anchors.top: parent.top
anchors.left: parent.left
text: qsTr("Local Documents")
color: theme.titleTextColor
font.pixelSize: theme.fontSizeLarge
font.bold: true
}
ScrollView {
id: scrollView
anchors.top: listLabel.bottom
anchors.topMargin: 20
anchors.bottom: collectionSettings.top
anchors.bottomMargin: 20
anchors.left: parent.left
anchors.right: parent.right
clip: true
contentHeight: 300
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
background: Rectangle {
color: theme.controlBackground
}
ListView {
id: listView
model: LocalDocs.localDocsModel
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: ScrollBar {
parent: listView.parent
anchors.top: listView.top
anchors.left: listView.right
anchors.bottom: listView.bottom
}
delegate: Rectangle {
id: item
width: listView.width
height: collectionId.height + 40
color: index % 2 === 0 ? theme.darkContrast : theme.lightContrast
MyCheckBox {
id: checkBox
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.margins: 20
checked: currentChat.hasCollection(collection)
onClicked: {
if (checkBox.checked) {
currentChat.addCollection(collection)
} else {
currentChat.removeCollection(collection)
}
}
ToolTip.text: qsTr("Warning: searching collections while indexing can return incomplete results")
ToolTip.visible: hovered && model.indexing
}
Text {
id: collectionId
anchors.verticalCenter: parent.verticalCenter
anchors.left: checkBox.right
anchors.margins: 20
anchors.leftMargin: 10
text: collection
font.pixelSize: theme.fontSizeLarge
elide: Text.ElideRight
color: theme.textColor
}
ProgressBar {
id: itemProgressBar
anchors.verticalCenter: parent.verticalCenter
anchors.left: collectionId.right
anchors.right: parent.right
anchors.margins: 20
anchors.leftMargin: 40
visible: model.indexing || model.currentEmbeddingsToIndex !== model.totalEmbeddingsToIndex || model.error !== ""
value: model.error !== "" ? 0 : model.indexing ?
(model.totalBytesToIndex - model.currentBytesToIndex) / model.totalBytesToIndex :
(model.currentEmbeddingsToIndex / model.totalEmbeddingsToIndex)
background: Rectangle {
implicitHeight: 45
color: model.error ? theme.textErrorColor : theme.progressBackground
radius: 3
}
contentItem: Item {
implicitHeight: 40
Rectangle {
width: itemProgressBar.visualPosition * parent.width
height: parent.height
radius: 2
color: theme.progressForeground
}
}
Accessible.role: Accessible.ProgressBar
Accessible.name: qsTr("Indexing progressBar")
Accessible.description: qsTr("Shows the progress made in the indexing")
ToolTip.text: model.error
ToolTip.visible: hovered && model.error !== ""
}
Label {
id: speedLabel
color: theme.progressText
visible: model.indexing || model.currentEmbeddingsToIndex !== model.totalEmbeddingsToIndex
anchors.verticalCenter: itemProgressBar.verticalCenter
anchors.left: itemProgressBar.left
anchors.right: itemProgressBar.right
horizontalAlignment: Text.AlignHCenter
text: model.error !== "" ? qsTr("error...") : (model.indexing ? qsTr("indexing...") : qsTr("embeddings..."))
elide: Text.ElideRight
font.pixelSize: theme.fontSizeLarge
}
}
}
}
MySettingsButton {
id: collectionSettings
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Add & Remove")
font.pixelSize: theme.fontSizeLarger
onClicked: {
addRemoveClicked()
}
}
}

View File

@ -0,0 +1,148 @@
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
import QtQuick.Dialogs
import chatlistmodel
import localdocs
import llm
Rectangle {
id: collectionsDrawer
color: "transparent"
signal addDocsClicked
property var currentChat: ChatListModel.currentChat
Rectangle {
id: borderLeft
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
width: 2
color: theme.dividerColor
}
ScrollView {
id: scrollView
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: borderLeft.right
anchors.right: parent.right
anchors.margins: 15
clip: true
contentHeight: 300
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
ListView {
id: listView
model: LocalDocs.localDocsModel
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: ScrollBar {
parent: listView.parent
anchors.top: listView.top
anchors.left: listView.right
anchors.bottom: listView.bottom
}
spacing: 15
delegate: Rectangle {
width: listView.width
height: childrenRect.height + 15
color: checkBox.checked ? theme.collectionsButtonBackground : "transparent"
RowLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 7.5
MyCheckBox {
id: checkBox
Layout.alignment: Qt.AlignLeft
checked: currentChat.hasCollection(collection)
onClicked: {
if (checkBox.checked) {
currentChat.addCollection(collection)
} else {
currentChat.removeCollection(collection)
}
}
ToolTip.text: qsTr("Warning: searching collections while indexing can return incomplete results")
ToolTip.visible: hovered && model.indexing
}
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
text: collection
font.pixelSize: theme.fontSizeLarger
elide: Text.ElideRight
color: theme.textColor
}
Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
text: "%1 %2".arg(qsTr("%n file(s)", "", model.totalDocs)).arg(qsTr("%n word(s)", "", model.totalWords))
elide: Text.ElideRight
color: theme.mutedTextColor
font.pixelSize: theme.fontSizeSmaller
}
RowLayout {
visible: model.updating
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
MyBusyIndicator {
color: theme.accentColor
size: 24
Layout.minimumWidth: 24
Layout.minimumHeight: 24
}
Text {
text: qsTr("Updating")
elide: Text.ElideRight
color: theme.accentColor
font.pixelSize: theme.fontSizeSmaller
font.bold: true
}
}
}
}
}
footer: ColumnLayout {
width: listView.width
spacing: 30
Rectangle {
visible: listView.count !== 0
Layout.topMargin: 30
Layout.fillWidth: true
height: 1
color: theme.dividerColor
}
MySettingsButton {
id: collectionSettings
enabled: LocalDocs.databaseValid
Layout.alignment: Qt.AlignCenter
text: qsTr("\uFF0B Add Docs")
font.pixelSize: theme.fontSizeLarger
onClicked: {
addDocsClicked()
}
}
Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
text: qsTr("Chat privately with local files using on-device Large Language Models (LLMs). Keeps data private and secure. Best results with Llama 3 Instruct.")
font.pixelSize: theme.fontSizeLarger
wrapMode: Text.WordWrap
elide: Text.ElideRight
color: theme.mutedTextColor
}
}
}
}
}

View File

@ -0,0 +1,278 @@
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import llm
import chatlistmodel
import download
import modellist
import network
import gpt4all
import mysettings
Rectangle {
id: homeView
Theme {
id: theme
}
color: theme.viewBackground
signal chatViewRequested()
signal localDocsViewRequested()
signal settingsViewRequested(int page)
signal addModelViewRequested()
property bool shouldShowFirstStart: false
ColumnLayout {
id: mainArea
anchors.fill: parent
anchors.margins: 30
spacing: 30
ColumnLayout {
Layout.fillWidth: true
Layout.maximumWidth: 1530
Layout.alignment: Qt.AlignCenter
spacing: 30
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 5
Text {
id: welcome
Layout.alignment: Qt.AlignHCenter
text: qsTr("Welcome to GPT4All")
font.pixelSize: theme.fontSizeBanner
color: theme.titleTextColor
}
Text {
Layout.alignment: Qt.AlignHCenter
text: qsTr("the privacy-first LLM chat application")
font.pixelSize: theme.fontSizeLarge
color: theme.titleInfoTextColor
}
}
MyButton {
id: startChat
visible: shouldShowFirstStart
Layout.alignment: Qt.AlignHCenter
text: qsTr("Start chatting")
onClicked: {
chatViewRequested()
}
}
RowLayout {
spacing: 15
visible: !startChat.visible
Layout.alignment: Qt.AlignHCenter
MyWelcomeButton {
Layout.fillWidth: true
Layout.maximumWidth: 500
Layout.preferredHeight: 150
text: qsTr("Start Chatting")
description: qsTr("Chat with any LLM")
imageSource: "qrc:/gpt4all/icons/chat.svg"
onClicked: {
chatViewRequested()
}
}
MyWelcomeButton {
Layout.fillWidth: true
Layout.maximumWidth: 500
Layout.preferredHeight: 150
text: qsTr("LocalDocs")
description: qsTr("Chat with your local files")
imageSource: "qrc:/gpt4all/icons/db.svg"
onClicked: {
localDocsViewRequested()
}
}
MyWelcomeButton {
Layout.fillWidth: true
Layout.maximumWidth: 500
Layout.preferredHeight: 150
text: qsTr("Find Models")
description: qsTr("Explore and download models")
imageSource: "qrc:/gpt4all/icons/models.svg"
onClicked: {
addModelViewRequested()
}
}
}
Item {
visible: !startChat.visible && Download.latestNews !== ""
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: 120
Layout.maximumHeight: textAreaNews.height
Rectangle {
id: roundedFrameNews // latest news
anchors.fill: parent
z: 299
radius: 10
border.width: 1
border.color: theme.controlBorder
color: "transparent"
clip: true
}
Item {
anchors.fill: parent
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: roundedFrameNews.width
height: roundedFrameNews.height
radius: 10
}
}
RowLayout {
spacing: 0
anchors.fill: parent
Rectangle {
color: "transparent"
width: 82
height: 100
Image {
id: newsImg
anchors.centerIn: parent
sourceSize: Qt.size(40, 40)
mipmap: true
visible: false
source: "qrc:/gpt4all/icons/alt_logo.svg"
}
ColorOverlay {
anchors.fill: newsImg
source: newsImg
color: theme.styledTextColor
}
}
Item {
id: myItem
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle {
anchors.fill: parent
color: theme.conversationBackground
}
ScrollView {
id: newsScroll
anchors.fill: parent
clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
Text {
id: textAreaNews
width: myItem.width
padding: 20
color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarger
textFormat: TextEdit.MarkdownText
wrapMode: Text.WordWrap
text: Download.latestNews
focus: false
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Latest news")
Accessible.description: qsTr("Latest news from GPT4All")
}
}
}
}
}
}
}
Rectangle {
id: linkBar
Layout.alignment: Qt.AlignBottom
Layout.fillWidth: true
border.width: 1
border.color: theme.dividerColor
radius: 6
z: 200
height: 30
color: theme.conversationBackground
RowLayout {
anchors.fill: parent
spacing: 0
RowLayout {
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
spacing: 4
MyFancyLink {
text: qsTr("Release Notes")
imageSource: "qrc:/gpt4all/icons/notes.svg"
onClicked: { Qt.openUrlExternally("https://github.com/nomic-ai/gpt4all/releases") }
}
MyFancyLink {
text: qsTr("Discord")
imageSource: "qrc:/gpt4all/icons/discord.svg"
onClicked: { Qt.openUrlExternally("https://discord.gg/4M2QFmTt2k") }
}
MyFancyLink {
text: qsTr("X (Twitter)")
imageSource: "qrc:/gpt4all/icons/twitter.svg"
onClicked: { Qt.openUrlExternally("https://twitter.com/nomic_ai") }
}
MyFancyLink {
text: qsTr("Github")
imageSource: "qrc:/gpt4all/icons/github.svg"
onClicked: { Qt.openUrlExternally("https://github.com/nomic-ai/gpt4all") }
}
}
RowLayout {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
spacing: 40
MyFancyLink {
text: qsTr("GPT4All.io")
imageSource: "qrc:/gpt4all/icons/globe.svg"
onClicked: { Qt.openUrlExternally("https://gpt4all.io") }
rightPadding: 15
}
}
}
}
}
Rectangle {
anchors.top: mainArea.top
anchors.right: mainArea.right
border.width: 1
border.color: theme.dividerColor
radius: 6
z: 200
height: 30
color: theme.conversationBackground
width: subscribeLink.width
RowLayout {
anchors.fill: parent
MyFancyLink {
id: subscribeLink
Layout.alignment: Qt.AlignCenter
text: qsTr("Subscribe to Newsletter")
imageSource: "qrc:/gpt4all/icons/email.svg"
onClicked: { Qt.openUrlExternally("https://forms.nomic.ai/gpt4all-release-notes-signup") }
}
}
}
}

View File

@ -14,187 +14,155 @@ MySettingsTab {
MySettings.restoreLocalDocsDefaults(); MySettings.restoreLocalDocsDefaults();
} }
property bool hasEmbeddingModel: ModelList.installedEmbeddingModels.count !== 0 showRestoreDefaultsButton: true
showAdvancedSettingsButton: hasEmbeddingModel
showRestoreDefaultsButton: hasEmbeddingModel
title: qsTr("LocalDocs") title: qsTr("LocalDocs")
contentItem: ColumnLayout { contentItem: ColumnLayout {
id: root id: root
spacing: 10 spacing: 10
property alias collection: collection.text Label {
property alias folder_path: folderEdit.text color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarge
MySettingsLabel { font.bold: true
id: downloadLabel text: "Indexing"
Layout.fillWidth: true
Layout.maximumWidth: parent.width
wrapMode: Text.Wrap
visible: !hasEmbeddingModel
Layout.alignment: Qt.AlignLeft
text: qsTr("This feature requires the download of a text embedding model in order to index documents for later search. Please download the <b>SBert</a> text embedding model from the download dialog to proceed.")
} }
MySettingsButton { Rectangle {
visible: !hasEmbeddingModel Layout.bottomMargin: 15
Layout.topMargin: 20 Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft height: 2
text: qsTr("Download") color: theme.settingsDivider
font.pixelSize: theme.fontSizeLarger }
onClicked: {
downloadClicked() RowLayout {
MySettingsLabel {
id: extsLabel
text: qsTr("Allowed File Extensions")
helpText: qsTr("Comma-separated list. LocalDocs will only attempt to process files with these extensions.")
}
MyTextField {
id: extsField
text: MySettings.localDocsFileExtensions.join(',')
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
Layout.alignment: Qt.AlignRight
Layout.minimumWidth: 200
validator: RegularExpressionValidator {
regularExpression: /([^ ,\/"']+,?)*/
}
onEditingFinished: {
// split and remove empty elements
var exts = text.split(',').filter(e => e);
// normalize and deduplicate
exts = exts.map(e => e.toLowerCase());
exts = Array.from(new Set(exts));
/* Blacklist common unsupported file extensions. We only support plain text and PDFs, and although we
* reject binary data, we don't want to waste time trying to index files that we don't support. */
exts = exts.filter(e => ![
/* Microsoft documents */ "rtf", "docx", "ppt", "pptx", "xls", "xlsx",
/* OpenOffice */ "odt", "ods", "odp", "odg",
/* photos */ "jpg", "jpeg", "png", "gif", "bmp", "tif", "tiff", "webp",
/* audio */ "mp3", "wma", "m4a", "wav", "flac",
/* videos */ "mp4", "mov", "webm", "mkv", "avi", "flv", "wmv",
/* executables */ "exe", "com", "dll", "so", "dylib", "msi",
/* binary images */ "iso", "img", "dmg",
/* archives */ "zip", "jar", "apk", "rar", "7z", "tar", "gz", "xz", "bz2", "tar.gz",
"tgz", "tar.xz", "tar.bz2",
/* misc */ "bin",
].includes(e));
MySettings.localDocsFileExtensions = exts;
extsField.text = exts.join(',');
focus = false;
}
Accessible.role: Accessible.EditableText
Accessible.name: extsLabel.text
Accessible.description: extsLabel.helpText
} }
} }
Item { Label {
visible: hasEmbeddingModel Layout.topMargin: 15
Layout.fillWidth: true color: theme.grayRed900
height: row.height font.pixelSize: theme.fontSizeLarge
RowLayout { font.bold: true
id: row text: "Embedding"
anchors.left: parent.left
anchors.right: parent.right
height: collection.height
spacing: 10
MyTextField {
id: collection
width: 225
horizontalAlignment: Text.AlignJustify
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
placeholderText: qsTr("Collection name...")
placeholderTextColor: theme.mutedTextColor
ToolTip.text: qsTr("Name of the collection to add (Required)")
ToolTip.visible: hovered
Accessible.role: Accessible.EditableText
Accessible.name: collection.text
Accessible.description: ToolTip.text
function showError() {
collection.placeholderTextColor = theme.textErrorColor
}
onTextChanged: {
collection.placeholderTextColor = theme.mutedTextColor
}
}
MyDirectoryField {
id: folderEdit
Layout.fillWidth: true
text: root.folder_path
placeholderText: qsTr("Folder path...")
font.pixelSize: theme.fontSizeLarge
placeholderTextColor: theme.mutedTextColor
ToolTip.text: qsTr("Folder path to documents (Required)")
ToolTip.visible: hovered
function showError() {
folderEdit.placeholderTextColor = theme.textErrorColor
}
onTextChanged: {
folderEdit.placeholderTextColor = theme.mutedTextColor
}
}
MySettingsButton {
id: browseButton
text: qsTr("Browse")
onClicked: {
openFolderDialog(StandardPaths.writableLocation(StandardPaths.HomeLocation), function(selectedFolder) {
root.folder_path = selectedFolder
})
}
}
MySettingsButton {
id: addButton
text: qsTr("Add")
Accessible.role: Accessible.Button
Accessible.name: text
Accessible.description: qsTr("Add collection")
onClicked: {
var isError = false;
if (root.collection === "") {
isError = true;
collection.showError();
}
if (root.folder_path === "" || !folderEdit.isValid) {
isError = true;
folderEdit.showError();
}
if (isError)
return;
LocalDocs.addFolder(root.collection, root.folder_path)
root.collection = ""
root.folder_path = ""
collection.clear()
}
}
}
} }
ColumnLayout { Rectangle {
visible: hasEmbeddingModel Layout.bottomMargin: 15
spacing: 0 Layout.fillWidth: true
Repeater { height: 2
model: LocalDocs.localDocsModel color: theme.grayRed500
delegate: Rectangle { }
id: item
Layout.fillWidth: true
height: buttons.height + 20
color: index % 2 === 0 ? theme.darkContrast : theme.lightContrast
property bool removing: false
Text { RowLayout {
id: collectionId MySettingsLabel {
anchors.verticalCenter: parent.verticalCenter text: qsTr("Use Nomic Embed API")
anchors.left: parent.left helpText: qsTr("Embed documents using the fast Nomic API instead of a private local model.")
anchors.margins: 20 }
text: collection
elide: Text.ElideRight
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
width: 200
}
Text { MyCheckBox {
id: folderId id: useNomicAPIBox
anchors.left: collectionId.right Component.onCompleted: {
anchors.right: buttons.left useNomicAPIBox.checked = MySettings.localDocsUseRemoteEmbed;
anchors.margins: 20 }
anchors.verticalCenter: parent.verticalCenter onClicked: {
text: folder_path MySettings.localDocsUseRemoteEmbed = useNomicAPIBox.checked && MySettings.localDocsNomicAPIKey !== "";
elide: Text.ElideRight
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
}
Item {
id: buttons
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: 20
width: removeButton.width
height:removeButton.height
MySettingsButton {
id: removeButton
anchors.centerIn: parent
text: qsTr("Remove")
visible: !item.removing
onClicked: {
item.removing = true
LocalDocs.removeFolder(collection, folder_path)
}
}
}
} }
} }
} }
RowLayout { RowLayout {
visible: hasEmbeddingModel MySettingsLabel {
id: apiKeyLabel
text: qsTr("Nomic API Key")
helpText: qsTr("API key to use for Nomic Embed. Get one at https://atlas.nomic.ai/")
}
MyTextField {
id: apiKeyField
text: MySettings.localDocsNomicAPIKey
color: apiKeyField.acceptableInput ? theme.textColor : theme.textErrorColor
font.pixelSize: theme.fontSizeLarge
Layout.alignment: Qt.AlignRight
Layout.minimumWidth: 200
enabled: useNomicAPIBox.checked
validator: RegularExpressionValidator {
// may be empty
regularExpression: /|nk-[a-zA-Z0-9_-]{43}/
}
onEditingFinished: {
MySettings.localDocsNomicAPIKey = apiKeyField.text;
MySettings.localDocsUseRemoteEmbed = useNomicAPIBox.checked && MySettings.localDocsNomicAPIKey !== "";
focus = false;
}
Accessible.role: Accessible.EditableText
Accessible.name: apiKeyLabel.text
Accessible.description: apiKeyLabel.helpText
}
}
Label {
Layout.topMargin: 15
color: theme.grayRed900
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: "Display"
}
Rectangle {
Layout.bottomMargin: 15
Layout.fillWidth: true
height: 2
color: theme.grayRed500
}
RowLayout {
MySettingsLabel { MySettingsLabel {
id: showReferencesLabel id: showReferencesLabel
text: qsTr("Show references") text: qsTr("Show sources")
helpText: qsTr("Shows sources in GUI generated by localdocs")
} }
MyCheckBox { MyCheckBox {
id: showReferencesBox id: showReferencesBox
@ -202,104 +170,92 @@ MySettingsTab {
onClicked: { onClicked: {
MySettings.localDocsShowReferences = !MySettings.localDocsShowReferences MySettings.localDocsShowReferences = !MySettings.localDocsShowReferences
} }
ToolTip.text: qsTr("Shows any references in GUI generated by localdocs")
ToolTip.visible: hovered
} }
} }
Label {
Layout.topMargin: 15
color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: "Advanced"
}
Rectangle { Rectangle {
visible: hasEmbeddingModel Layout.bottomMargin: 15
Layout.fillWidth: true Layout.fillWidth: true
height: 3 height: 2
color: theme.accentColor color: theme.settingsDivider
}
}
advancedSettings: GridLayout {
id: gridLayout
columns: 3
rowSpacing: 10
columnSpacing: 10
visible: hasEmbeddingModel
Rectangle {
Layout.row: 3
Layout.column: 0
Layout.fillWidth: true
Layout.columnSpan: 3
height: 3
color: theme.accentColor
} }
MySettingsLabel { MySettingsLabel {
id: chunkLabel id: warningLabel
Layout.row: 1 Layout.bottomMargin: 15
Layout.column: 0
text: qsTr("Document snippet size (characters)")
}
MyTextField {
id: chunkSizeTextField
Layout.row: 1
Layout.column: 1
ToolTip.text: qsTr("Number of characters per document snippet.\nNOTE: larger numbers increase likelihood of factual responses, but also result in slower generation.")
ToolTip.visible: hovered
text: MySettings.localDocsChunkSize
validator: IntValidator {
bottom: 1
}
onEditingFinished: {
var val = parseInt(text)
if (!isNaN(val)) {
MySettings.localDocsChunkSize = val
focus = false
} else {
text = MySettings.localDocsChunkSize
}
}
}
MySettingsLabel {
id: contextItemsPerPrompt
Layout.row: 2
Layout.column: 0
text: qsTr("Max document snippets per prompt")
}
MyTextField {
Layout.row: 2
Layout.column: 1
ToolTip.text: qsTr("Max best N matches of retrieved document snippets to add to the context for prompt.\nNOTE: larger numbers increase likelihood of factual responses, but also result in slower generation.")
ToolTip.visible: hovered
text: MySettings.localDocsRetrievalSize
validator: IntValidator {
bottom: 1
}
onEditingFinished: {
var val = parseInt(text)
if (!isNaN(val)) {
MySettings.localDocsRetrievalSize = val
focus = false
} else {
text = MySettings.localDocsRetrievalSize
}
}
}
Item {
Layout.row: 1
Layout.column: 2
Layout.rowSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignTop color: theme.textErrorColor
Layout.minimumHeight: warningLabel.height wrapMode: Text.WordWrap
text: qsTr("Warning: Advanced usage only.")
helpText: qsTr("Values too large may cause localdocs failure, extremely slow responses or failure to respond at all. Roughly speaking, the {N chars x N snippets} are added to the model's context window. More info <a href=\"https://docs.gpt4all.io/gpt4all_chat.html#localdocs-beta-plugin-chat-with-your-data\">here.</a>")
onLinkActivated: function(link) { Qt.openUrlExternally(link) }
}
RowLayout {
MySettingsLabel { MySettingsLabel {
id: warningLabel id: chunkLabel
width: parent.width Layout.fillWidth: true
color: theme.textErrorColor text: qsTr("Document snippet size (characters)")
wrapMode: Text.WordWrap helpText: qsTr("Number of characters per document snippet. Larger numbers increase likelihood of factual responses, but also result in slower generation.")
text: qsTr("Warning: Advanced usage only. Values too large may cause localdocs failure, extremely slow responses or failure to respond at all. Roughly speaking, the {N chars x N snippets} are added to the model's context window. More info <a href=\"https://docs.gpt4all.io/gpt4all_chat.html#localdocs-beta-plugin-chat-with-your-data\">here.</a>") }
onLinkActivated: function(link) { Qt.openUrlExternally(link) }
MyTextField {
id: chunkSizeTextField
text: MySettings.localDocsChunkSize
validator: IntValidator {
bottom: 1
}
onEditingFinished: {
var val = parseInt(text)
if (!isNaN(val)) {
MySettings.localDocsChunkSize = val
focus = false
} else {
text = MySettings.localDocsChunkSize
}
}
} }
} }
}
RowLayout {
Layout.topMargin: 15
MySettingsLabel {
id: contextItemsPerPrompt
text: qsTr("Max document snippets per prompt")
helpText: qsTr("Max best N matches of retrieved document snippets to add to the context for prompt. Larger numbers increase likelihood of factual responses, but also result in slower generation.")
}
MyTextField {
text: MySettings.localDocsRetrievalSize
validator: IntValidator {
bottom: 1
}
onEditingFinished: {
var val = parseInt(text)
if (!isNaN(val)) {
MySettings.localDocsRetrievalSize = val
focus = false
} else {
text = MySettings.localDocsRetrievalSize
}
}
}
}
Rectangle {
Layout.topMargin: 15
Layout.fillWidth: true
height: 2
color: theme.settingsDivider
}
}
} }

View File

@ -0,0 +1,457 @@
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import llm
import chatlistmodel
import download
import modellist
import network
import gpt4all
import mysettings
import localdocs
Rectangle {
id: localDocsView
Theme {
id: theme
}
color: theme.viewBackground
signal chatViewRequested()
signal localDocsViewRequested()
signal settingsViewRequested(int page)
signal addCollectionViewRequested()
ColumnLayout {
id: mainArea
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.margins: 30
spacing: 50
RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
visible: LocalDocs.databaseValid && LocalDocs.localDocsModel.count !== 0
spacing: 50
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
Layout.minimumWidth: 200
spacing: 5
Text {
id: welcome
text: qsTr("LocalDocs")
font.pixelSize: theme.fontSizeBanner
color: theme.titleTextColor
}
Text {
text: qsTr("Chat with your local files")
font.pixelSize: theme.fontSizeLarge
color: theme.titleInfoTextColor
}
}
Rectangle {
Layout.fillWidth: true
height: 0
}
MyButton {
Layout.alignment: Qt.AlignTop | Qt.AlignRight
text: qsTr("\uFF0B Add Doc Collection")
onClicked: {
addCollectionViewRequested()
}
}
}
Rectangle {
id: warning
Layout.fillWidth: true
Layout.fillHeight: true
visible: !LocalDocs.databaseValid
Text {
anchors.centerIn: parent
horizontalAlignment: Qt.AlignHCenter
text: qsTr("ERROR: The LocalDocs database is not valid.")
color: theme.textErrorColor
font.bold: true
font.pixelSize: theme.fontSizeLargest
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
visible: LocalDocs.databaseValid && LocalDocs.localDocsModel.count === 0
ColumnLayout {
id: noInstalledLabel
anchors.centerIn: parent
spacing: 0
Text {
Layout.alignment: Qt.AlignCenter
text: qsTr("No Collections Installed")
color: theme.mutedLightTextColor
font.pixelSize: theme.fontSizeBannerSmall
}
Text {
Layout.topMargin: 15
horizontalAlignment: Qt.AlignHCenter
color: theme.mutedLighterTextColor
text: qsTr("Install a collection of local documents to get started using this feature")
font.pixelSize: theme.fontSizeLarge
}
}
MyButton {
anchors.top: noInstalledLabel.bottom
anchors.topMargin: 50
anchors.horizontalCenter: noInstalledLabel.horizontalCenter
rightPadding: 60
leftPadding: 60
text: qsTr("\uFF0B Add Doc Collection")
onClicked: {
addCollectionViewRequested()
}
Accessible.role: Accessible.Button
Accessible.name: qsTr("Shows the add model view")
}
}
ScrollView {
id: scrollView
ScrollBar.vertical.policy: ScrollBar.AsNeeded
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
visible: LocalDocs.databaseValid && LocalDocs.localDocsModel.count !== 0
ListView {
id: collectionListView
model: LocalDocs.localDocsModel
boundsBehavior: Flickable.StopAtBounds
spacing: 30
delegate: Rectangle {
width: collectionListView.width
height: childrenRect.height + 60
color: theme.conversationBackground
radius: 10
border.width: 1
border.color: theme.controlBorder
property bool removing: false
ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 30
spacing: 10
RowLayout {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
text: collection
elide: Text.ElideRight
color: theme.titleTextColor
font.pixelSize: theme.fontSizeLargest
font.bold: true
}
Item {
Layout.alignment: Qt.AlignRight
Layout.preferredWidth: state.contentWidth + 50
Layout.preferredHeight: state.contentHeight + 10
ProgressBar {
id: itemProgressBar
anchors.fill: parent
value: {
if (model.error !== "")
return 0
if (model.indexing)
return (model.totalBytesToIndex - model.currentBytesToIndex) / model.totalBytesToIndex
if (model.currentEmbeddingsToIndex !== 0)
return (model.totalEmbeddingsToIndex - model.currentEmbeddingsToIndex) / model.totalEmbeddingsToIndex
return 0
}
background: Rectangle {
implicitHeight: 45
color: {
if (model.error !== "")
return "transparent"
if (model.indexing)
return theme.altProgressBackground
if (model.currentEmbeddingsToIndex !== 0)
return theme.altProgressBackground
if (model.forceIndexing)
return theme.red200
return theme.lightButtonBackground
}
radius: 6
}
contentItem: Item {
implicitHeight: 40
Rectangle {
width: itemProgressBar.visualPosition * parent.width
height: parent.height
radius: 2
color: theme.altProgressForeground
}
}
Accessible.role: Accessible.ProgressBar
Accessible.name: qsTr("Indexing progressBar")
Accessible.description: qsTr("Shows the progress made in the indexing")
ToolTip.text: model.error
ToolTip.visible: hovered && model.error !== ""
}
Label {
id: state
anchors.centerIn: itemProgressBar
horizontalAlignment: Text.AlignHCenter
color: {
if (model.error !== "")
return theme.textErrorColor
if (model.indexing)
return theme.altProgressText
if (model.currentEmbeddingsToIndex !== 0)
return theme.altProgressText
if (model.forceIndexing)
return theme.textErrorColor
return theme.lighterButtonForeground
}
text: {
if (model.error !== "")
return qsTr("ERROR")
// indicates extracting snippets from documents
if (model.indexing)
return qsTr("INDEXING")
// indicates generating the embeddings for any outstanding snippets
if (model.currentEmbeddingsToIndex !== 0)
return qsTr("EMBEDDING")
if (model.forceIndexing)
return qsTr("REQUIRES UPDATE")
if (model.installed)
return qsTr("READY")
return qsTr("INSTALLING")
}
elide: Text.ElideRight
font.bold: true
font.pixelSize: theme.fontSizeSmaller
}
}
}
RowLayout {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
text: folder_path
elide: Text.ElideRight
color: theme.titleTextColor2
font.pixelSize: theme.fontSizeSmall
}
Text {
Layout.alignment: Qt.AlignRight
text: {
if (model.error !== "")
return model.error
if (model.indexing)
return qsTr("Indexing in progress")
if (model.currentEmbeddingsToIndex !== 0)
return qsTr("Embedding in progress")
if (model.forceIndexing)
return qsTr("This collection requires an update after version change")
if (model.installed)
return qsTr("Automatically reindexes upon changes to the folder")
return qsTr("Installation in progress")
}
elide: Text.ElideRight
color: theme.mutedDarkTextColor
font.pixelSize: theme.fontSizeSmaller
}
Text {
visible: {
return model.indexing || model.currentEmbeddingsToIndex !== 0
}
Layout.alignment: Qt.AlignRight
text: {
var percentComplete = Math.round(itemProgressBar.value * 100);
var formattedPercent = percentComplete < 10 ? " " + percentComplete : percentComplete.toString();
return formattedPercent + qsTr("%")
}
elide: Text.ElideRight
color: theme.mutedDarkTextColor
font.family: "monospace"
font.pixelSize: theme.fontSizeSmaller
}
}
RowLayout {
spacing: 7
Text {
text: "%1 %2".arg(qsTr("%n file(s)", "", model.totalDocs)).arg(qsTr("%n word(s)", "", model.totalWords))
elide: Text.ElideRight
color: theme.styledTextColor2
font.pixelSize: theme.fontSizeSmaller
}
Text {
text: model.embeddingModel
elide: Text.ElideRight
color: theme.mutedDarkTextColor
font.bold: true
font.pixelSize: theme.fontSizeSmaller
}
Text {
visible: Qt.formatDateTime(model.lastUpdate) !== ""
text: Qt.formatDateTime(model.lastUpdate)
elide: Text.ElideRight
color: theme.mutedTextColor
font.pixelSize: theme.fontSizeSmaller
}
Text {
visible: model.currentEmbeddingsToIndex !== 0
text: (model.totalEmbeddingsToIndex - model.currentEmbeddingsToIndex) + " of "
+ model.totalEmbeddingsToIndex + " embeddings"
elide: Text.ElideRight
color: theme.mutedTextColor
font.pixelSize: theme.fontSizeSmaller
}
}
Rectangle {
Layout.fillWidth: true
height: 1
color: theme.dividerColor
}
RowLayout {
id: fileProcessingRow
Layout.topMargin: 15
Layout.bottomMargin: 15
visible: model.fileCurrentlyProcessing !== "" && (model.indexing || model.currentEmbeddingsToIndex !== 0)
MyBusyIndicator {
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: 12
Layout.preferredHeight: 12
running: true
size: 12
color: theme.textColor
}
Text {
id: filename
Layout.alignment: Qt.AlignCenter
text: model.fileCurrentlyProcessing
elide: Text.ElideRight
color: theme.textColor
font.bold: true
font.pixelSize: theme.fontSizeLarge
}
}
Rectangle {
visible: fileProcessingRow.visible
Layout.fillWidth: true
height: 1
color: theme.dividerColor
}
RowLayout {
Layout.fillWidth: true
spacing: 30
Layout.leftMargin: 15
Layout.topMargin: 15
Text {
text: qsTr("Remove")
elide: Text.ElideRight
color: theme.red500
font.bold: true
font.pixelSize: theme.fontSizeSmall
TapHandler {
onTapped: {
LocalDocs.removeFolder(collection, folder_path)
}
}
}
Text {
Layout.alignment: Qt.AlignRight
visible: !model.forceIndexing && !model.indexing && model.currentEmbeddingsToIndex === 0
text: qsTr("Rebuild")
elide: Text.ElideRight
color: theme.red500
font.bold: true
font.pixelSize: theme.fontSizeSmall
TapHandler {
onTapped: { LocalDocs.forceRebuildFolder(folder_path); }
}
HoverHandler { id: hoverHandler1 }
ToolTip.text: qsTr("Reindex this folder from scratch. This is slow and usually not needed.")
ToolTip.visible: hoverHandler1.hovered
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
}
Item {
Layout.fillWidth: true
}
Text {
Layout.alignment: Qt.AlignRight
visible: model.forceIndexing
text: qsTr("Update")
elide: Text.ElideRight
color: theme.red500
font.bold: true
font.pixelSize: theme.fontSizeSmall
TapHandler {
onTapped: { LocalDocs.forceIndexing(collection); }
}
HoverHandler { id: hoverHandler2 }
ToolTip.text: qsTr("Update the collection to the new version. This is a slow operation.")
ToolTip.visible: hoverHandler2.hovered
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
}
}
}
}
}
}
}
}

View File

@ -1,643 +0,0 @@
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Dialogs
import QtQuick.Layouts
import chatlistmodel
import download
import llm
import modellist
import network
import mysettings
MyDialog {
id: modelDownloaderDialog
modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
padding: 10
property bool showEmbeddingModels: false
onOpened: {
Network.trackEvent("download_dialog")
if (showEmbeddingModels) {
ModelList.downloadableModels.expanded = true
var targetModelIndex = ModelList.defaultEmbeddingModelIndex
modelListView.positionViewAtIndex(targetModelIndex, ListView.Beginning)
}
}
PopupDialog {
id: downloadingErrorPopup
anchors.centerIn: parent
shouldTimeOut: false
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 30
Label {
id: listLabel
text: qsTr("Discover and Download Models")
visible: true
Layout.fillWidth: true
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
color: theme.titleTextColor
font.pixelSize: theme.fontSizeLargest
font.bold: true
}
RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignCenter
Layout.margins: 0
spacing: 10
MyTextField {
id: discoverField
property string textBeingSearched: ""
readOnly: ModelList.discoverInProgress
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: 720
Layout.preferredHeight: 90
font.pixelSize: theme.fontSizeLarger
placeholderText: qsTr("Discover and download models by keyword search...")
Accessible.role: Accessible.EditableText
Accessible.name: placeholderText
Accessible.description: qsTr("Text field for discovering and filtering downloadable models")
Connections {
target: ModelList
function onDiscoverInProgressChanged() {
if (ModelList.discoverInProgress) {
discoverField.textBeingSearched = discoverField.text;
discoverField.text = qsTr("Searching \u00B7 ") + discoverField.textBeingSearched;
} else {
discoverField.text = discoverField.textBeingSearched;
discoverField.textBeingSearched = "";
}
}
}
background: ProgressBar {
id: discoverProgressBar
indeterminate: ModelList.discoverInProgress && ModelList.discoverProgress === 0.0
value: ModelList.discoverProgress
background: Rectangle {
color: theme.controlBackground
radius: 10
}
contentItem: Item {
Rectangle {
visible: ModelList.discoverInProgress
anchors.bottom: parent.bottom
width: discoverProgressBar.visualPosition * parent.width
height: 10
radius: 2
color: theme.progressForeground
}
}
}
Keys.onReturnPressed: (event)=> {
if (event.modifiers & Qt.ControlModifier || event.modifiers & Qt.ShiftModifier)
event.accepted = false;
else {
editingFinished();
sendDiscovery()
}
}
function sendDiscovery() {
ModelList.downloadableModels.discoverAndFilter(discoverField.text);
}
RowLayout {
spacing: 0
anchors.right: discoverField.right
anchors.verticalCenter: discoverField.verticalCenter
anchors.rightMargin: 15
visible: !ModelList.discoverInProgress
MyMiniButton {
id: clearDiscoverButton
backgroundColor: theme.textColor
backgroundColorHovered: theme.iconBackgroundDark
visible: discoverField.text !== ""
contentItem: Text {
color: clearDiscoverButton.hovered ? theme.iconBackgroundDark : theme.textColor
text: "\u2715"
font.pixelSize: theme.fontSizeLarge
}
onClicked: {
discoverField.text = ""
discoverField.sendDiscovery() // should clear results
}
}
MyMiniButton {
backgroundColor: theme.textColor
backgroundColorHovered: theme.iconBackgroundDark
source: "qrc:/gpt4all/icons/settings.svg"
onClicked: {
discoveryTools.visible = !discoveryTools.visible
}
}
MyMiniButton {
id: sendButton
enabled: !ModelList.discoverInProgress
backgroundColor: theme.textColor
backgroundColorHovered: theme.iconBackgroundDark
source: "qrc:/gpt4all/icons/send_message.svg"
Accessible.name: qsTr("Initiate model discovery and filtering")
Accessible.description: qsTr("Triggers discovery and filtering of models")
onClicked: {
discoverField.sendDiscovery()
}
}
}
}
}
RowLayout {
id: discoveryTools
Layout.fillWidth: true
Layout.alignment: Qt.AlignCenter
Layout.margins: 0
spacing: 20
visible: false
MyComboBox {
id: comboSort
model: [qsTr("Default"), qsTr("Likes"), qsTr("Downloads"), qsTr("Recent")]
currentIndex: ModelList.discoverSort
contentItem: Text {
anchors.horizontalCenter: parent.horizontalCenter
rightPadding: 30
color: theme.textColor
text: {
return qsTr("Sort by: ") + comboSort.displayText
}
font.pixelSize: theme.fontSizeLarger
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
onActivated: function (index) {
ModelList.discoverSort = index;
}
}
MyComboBox {
id: comboSortDirection
model: [qsTr("Asc"), qsTr("Desc")]
currentIndex: {
if (ModelList.discoverSortDirection === 1)
return 0
else
return 1;
}
contentItem: Text {
anchors.horizontalCenter: parent.horizontalCenter
rightPadding: 30
color: theme.textColor
text: {
return qsTr("Sort dir: ") + comboSortDirection.displayText
}
font.pixelSize: theme.fontSizeLarger
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
onActivated: function (index) {
if (index === 0)
ModelList.discoverSortDirection = 1;
else
ModelList.discoverSortDirection = -1;
}
}
MyComboBox {
id: comboLimit
model: ["5", "10", "20", "50", "100", qsTr("None")]
currentIndex: {
if (ModelList.discoverLimit === 5)
return 0;
else if (ModelList.discoverLimit === 10)
return 1;
else if (ModelList.discoverLimit === 20)
return 2;
else if (ModelList.discoverLimit === 50)
return 3;
else if (ModelList.discoverLimit === 100)
return 4;
else if (ModelList.discoverLimit === -1)
return 5;
}
contentItem: Text {
anchors.horizontalCenter: parent.horizontalCenter
rightPadding: 30
color: theme.textColor
text: {
return qsTr("Limit: ") + comboLimit.displayText
}
font.pixelSize: theme.fontSizeLarger
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
onActivated: function (index) {
switch (index) {
case 0:
ModelList.discoverLimit = 5; break;
case 1:
ModelList.discoverLimit = 10; break;
case 2:
ModelList.discoverLimit = 20; break;
case 3:
ModelList.discoverLimit = 50; break;
case 4:
ModelList.discoverLimit = 100; break;
case 5:
ModelList.discoverLimit = -1; break;
}
}
}
}
Label {
visible: !ModelList.downloadableModels.count && !ModelList.asyncModelRequestOngoing
Layout.fillWidth: true
Layout.fillHeight: true
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
text: qsTr("Network error: could not retrieve http://gpt4all.io/models/models3.json")
font.pixelSize: theme.fontSizeLarge
color: theme.mutedTextColor
}
MyBusyIndicator {
visible: !ModelList.downloadableModels.count && ModelList.asyncModelRequestOngoing
running: ModelList.asyncModelRequestOngoing
Accessible.role: Accessible.Animation
Layout.alignment: Qt.AlignCenter
Accessible.name: qsTr("Busy indicator")
Accessible.description: qsTr("Displayed when the models request is ongoing")
}
ScrollView {
id: scrollView
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
ListView {
id: modelListView
model: ModelList.downloadableModels
boundsBehavior: Flickable.StopAtBounds
delegate: Rectangle {
id: delegateItem
width: modelListView.width
height: childrenRect.height
color: index % 2 === 0 ? theme.darkContrast : theme.lightContrast
GridLayout {
columns: 2
width: parent.width
Text {
textFormat: Text.StyledText
text: "<h2>" + name + "</h2>"
font.pixelSize: theme.fontSizeLarger
Layout.row: 0
Layout.column: 0
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.columnSpan: 2
color: theme.titleTextColor
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Model file")
Accessible.description: qsTr("Model file to be downloaded")
}
Rectangle {
id: actionBox
width: childrenRect.width + 20
color: theme.containerBackground
border.color: theme.accentColor
border.width: 1
radius: 10
Layout.row: 1
Layout.column: 1
Layout.rightMargin: 20
Layout.bottomMargin: 20
Layout.fillHeight: true
Layout.minimumHeight: childrenRect.height + 20
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.rowSpan: 2
ColumnLayout {
spacing: 0
MySettingsButton {
id: downloadButton
text: isDownloading ? qsTr("Cancel") : isIncomplete ? qsTr("Resume") : qsTr("Download")
font.pixelSize: theme.fontSizeLarge
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
visible: !isOnline && !installed && !calcHash && downloadError === ""
Accessible.description: qsTr("Stop/restart/start the download")
onClicked: {
if (!isDownloading) {
Download.downloadModel(filename);
} else {
Download.cancelDownload(filename);
}
}
}
MySettingsDestructiveButton {
id: removeButton
text: qsTr("Remove")
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
visible: installed || downloadError !== ""
Accessible.description: qsTr("Remove model from filesystem")
onClicked: {
Download.removeModel(filename);
}
}
MySettingsButton {
id: installButton
visible: !installed && isOnline
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
text: qsTr("Install")
font.pixelSize: theme.fontSizeLarge
onClicked: {
if (apiKey.text === "")
apiKey.showError();
else
Download.installModel(filename, apiKey.text);
}
Accessible.role: Accessible.Button
Accessible.name: qsTr("Install")
Accessible.description: qsTr("Install online model")
}
ColumnLayout {
spacing: 0
Label {
Layout.topMargin: 20
Layout.leftMargin: 20
textFormat: Text.StyledText
text: "<strong><font size=\"1\">"
+ (qsTr("Status: ")
+ (installed ? qsTr("Installed")
: (downloadError !== "" ? qsTr("<a href=\"#error\">Error</a>")
: (isDownloading ? qsTr("Downloading") : qsTr("Available")))))
+ "</strong></font>"
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
linkColor: theme.textErrorColor
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: qsTr("Whether the file is already installed on your system")
onLinkActivated: {
downloadingErrorPopup.text = downloadError;
downloadingErrorPopup.open();
}
}
Label {
Layout.leftMargin: 20
textFormat: Text.StyledText
text: "<strong><font size=\"1\">"
+ (qsTr("File size: ") + filesize)
+ (ramrequired < 0 ? "" : "<br>" + (qsTr("RAM required: ") + (ramrequired > 0 ? ramrequired + " GB" : qsTr("minimal"))))
+ (parameters === "" ? "" : "<br>" + qsTr("Parameters: ") + parameters)
+ (quant === "" ? "" : "<br>" + (qsTr("Quantization: ") + quant))
+ (type === "" ? "" : "<br>" + (qsTr("Type: ") + type))
+ "</strong></font>"
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: qsTr("Metadata about the model")
onLinkActivated: {
downloadingErrorPopup.text = downloadError;
downloadingErrorPopup.open();
}
}
Label {
visible: LLM.systemTotalRAMInGB() < ramrequired
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.maximumWidth: 300
textFormat: Text.StyledText
text: qsTr("<strong><font size=\"2\">WARNING: Not recommended for your hardware.")
+ qsTr(" Model requires more memory (") + ramrequired
+ qsTr(" GB) than your system has available (")
+ LLM.systemTotalRAMInGBString() + ").</strong></font>"
color: theme.textErrorColor
font.pixelSize: theme.fontSizeLarge
wrapMode: Text.WordWrap
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: qsTr("Error for incompatible hardware")
onLinkActivated: {
downloadingErrorPopup.text = downloadError;
downloadingErrorPopup.open();
}
}
}
ColumnLayout {
visible: isDownloading && !calcHash
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
spacing: 20
ProgressBar {
id: itemProgressBar
Layout.fillWidth: true
width: 200
value: bytesReceived / bytesTotal
background: Rectangle {
implicitHeight: 45
color: theme.progressBackground
radius: 3
}
contentItem: Item {
implicitHeight: 40
Rectangle {
width: itemProgressBar.visualPosition * parent.width
height: parent.height
radius: 2
color: theme.progressForeground
}
}
Accessible.role: Accessible.ProgressBar
Accessible.name: qsTr("Download progressBar")
Accessible.description: qsTr("Shows the progress made in the download")
}
Label {
id: speedLabel
color: theme.textColor
Layout.alignment: Qt.AlignRight
text: speed
font.pixelSize: theme.fontSizeLarge
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Download speed")
Accessible.description: qsTr("Download speed in bytes/kilobytes/megabytes per second")
}
}
RowLayout {
visible: calcHash
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.maximumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
clip: true
Label {
id: calcHashLabel
color: theme.textColor
text: qsTr("Calculating...")
font.pixelSize: theme.fontSizeLarge
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: qsTr("Whether the file hash is being calculated")
}
MyBusyIndicator {
id: busyCalcHash
running: calcHash
Accessible.role: Accessible.Animation
Accessible.name: qsTr("Busy indicator")
Accessible.description: qsTr("Displayed when the file hash is being calculated")
}
}
MyTextField {
id: apiKey
visible: !installed && isOnline
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
wrapMode: Text.WrapAnywhere
function showError() {
apiKey.placeholderTextColor = theme.textErrorColor
}
onTextChanged: {
apiKey.placeholderTextColor = theme.mutedTextColor
}
placeholderText: qsTr("enter $API_KEY")
Accessible.role: Accessible.EditableText
Accessible.name: placeholderText
Accessible.description: qsTr("Whether the file hash is being calculated")
}
}
}
Text {
id: descriptionText
text: description
font.pixelSize: theme.fontSizeLarge
Layout.row: 1
Layout.column: 0
Layout.leftMargin: 20
Layout.bottomMargin: 20
Layout.maximumWidth: modelListView.width - actionBox.width - 60
wrapMode: Text.WordWrap
textFormat: Text.StyledText
color: theme.textColor
linkColor: theme.textColor
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Description")
Accessible.description: qsTr("File description")
onLinkActivated: Qt.openUrlExternally(link)
}
}
}
footer: Component {
Rectangle {
width: modelListView.width
height: expandButton.height + 80
color: ModelList.downloadableModels.count % 2 === 0 ? theme.darkContrast : theme.lightContrast
MySettingsButton {
id: expandButton
anchors.centerIn: parent
padding: 40
text: ModelList.downloadableModels.expanded ? qsTr("Show fewer models") : qsTr("Show more models")
onClicked: {
ModelList.downloadableModels.expanded = !ModelList.downloadableModels.expanded;
}
}
}
}
}
}
RowLayout {
Layout.alignment: Qt.AlignCenter
Layout.fillWidth: true
spacing: 20
FolderDialog {
id: modelPathDialog
title: "Please choose a directory"
currentFolder: "file://" + MySettings.modelPath
onAccepted: {
MySettings.modelPath = selectedFolder
}
}
MySettingsLabel {
id: modelPathLabel
text: qsTr("Download path")
font.pixelSize: theme.fontSizeLarge
color: theme.textColor
Layout.row: 1
Layout.column: 0
}
MyDirectoryField {
id: modelPathDisplayField
text: MySettings.modelPath
font.pixelSize: theme.fontSizeLarge
Layout.fillWidth: true
ToolTip.text: qsTr("Path where model files will be downloaded to")
ToolTip.visible: hovered
Accessible.role: Accessible.ToolTip
Accessible.name: modelPathDisplayField.text
Accessible.description: ToolTip.text
onEditingFinished: {
if (isValid) {
MySettings.modelPath = modelPathDisplayField.text
} else {
text = MySettings.modelPath
}
}
}
MySettingsButton {
text: qsTr("Browse")
Accessible.description: qsTr("Choose where to save model files")
onClicked: modelPathDialog.open()
}
}
}
}

View File

@ -17,21 +17,43 @@ MySettingsTab {
columns: 3 columns: 3
rowSpacing: 10 rowSpacing: 10
columnSpacing: 10 columnSpacing: 10
enabled: ModelList.installedModels.count !== 0
property var currentModelName: comboBox.currentText property var currentModelName: comboBox.currentText
property var currentModelId: comboBox.currentValue property var currentModelId: comboBox.currentValue
property var currentModelInfo: ModelList.modelInfo(root.currentModelId) property var currentModelInfo: ModelList.modelInfo(root.currentModelId)
MySettingsLabel { ColumnLayout {
id: label
Layout.row: 0 Layout.row: 0
Layout.column: 0 Layout.column: 0
Layout.columnSpan: 3
Layout.fillWidth: true
spacing: 10
Label {
color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: "General"
}
Rectangle {
Layout.fillWidth: true
height: 2
color: theme.settingsDivider
}
}
MySettingsLabel {
id: label
Layout.row: 1
Layout.column: 0
text: qsTr("Model/Character") text: qsTr("Model/Character")
helpText: qsTr("Select or clone a model and change its settings")
} }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.row: 1 Layout.row: 2
Layout.column: 0 Layout.column: 0
Layout.columnSpan: 2 Layout.columnSpan: 2
height: label.height + 20 height: label.height + 20
@ -95,20 +117,14 @@ MySettingsTab {
} }
RowLayout { RowLayout {
Layout.row: 2 Layout.row: 3
Layout.column: 0 Layout.column: 0
Layout.topMargin: 15 Layout.topMargin: 15
spacing: 10 spacing: 10
MySettingsLabel { MySettingsLabel {
id: uniqueNameLabel id: uniqueNameLabel
text: qsTr("Unique Name") text: qsTr("Unique Name")
} helpText: qsTr("Must contain a non-empty unique name")
MySettingsLabel {
id: uniqueNameLabelHelp
visible: false
text: qsTr("Must contain a non-empty unique name that does not match any existing model/character.")
color: theme.textErrorColor
wrapMode: TextArea.Wrap
} }
} }
@ -117,7 +133,7 @@ MySettingsTab {
text: root.currentModelName text: root.currentModelName
font.pixelSize: theme.fontSizeLarge font.pixelSize: theme.fontSizeLarge
enabled: root.currentModelInfo.isClone || root.currentModelInfo.description === "" enabled: root.currentModelInfo.isClone || root.currentModelInfo.description === ""
Layout.row: 3 Layout.row: 4
Layout.column: 0 Layout.column: 0
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
@ -137,14 +153,13 @@ MySettingsTab {
if (text !== "" && ModelList.isUniqueName(text)) { if (text !== "" && ModelList.isUniqueName(text)) {
MySettings.setModelName(root.currentModelInfo, text); MySettings.setModelName(root.currentModelInfo, text);
} }
uniqueNameLabelHelp.visible = root.currentModelInfo.name !== "" &&
(text === "" || (text !== root.currentModelInfo.name && !ModelList.isUniqueName(text)));
} }
} }
MySettingsLabel { MySettingsLabel {
text: qsTr("Model File") text: qsTr("Model File")
Layout.row: 4 helpText: qsTr("The filename of the selected model")
Layout.row: 5
Layout.column: 0 Layout.column: 0
Layout.topMargin: 15 Layout.topMargin: 15
} }
@ -153,7 +168,7 @@ MySettingsTab {
text: root.currentModelInfo.filename text: root.currentModelInfo.filename
font.pixelSize: theme.fontSizeLarge font.pixelSize: theme.fontSizeLarge
enabled: false enabled: false
Layout.row: 5 Layout.row: 6
Layout.column: 0 Layout.column: 0
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
@ -162,7 +177,8 @@ MySettingsTab {
MySettingsLabel { MySettingsLabel {
visible: !root.currentModelInfo.isOnline visible: !root.currentModelInfo.isOnline
text: qsTr("System Prompt") text: qsTr("System Prompt")
Layout.row: 6 helpText: qsTr("Prefixed at the beginning of every conversation")
Layout.row: 7
Layout.column: 0 Layout.column: 0
Layout.topMargin: 15 Layout.topMargin: 15
} }
@ -170,7 +186,7 @@ MySettingsTab {
Rectangle { Rectangle {
id: systemPrompt id: systemPrompt
visible: !root.currentModelInfo.isOnline visible: !root.currentModelInfo.isOnline
Layout.row: 7 Layout.row: 8
Layout.column: 0 Layout.column: 0
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
@ -203,7 +219,7 @@ MySettingsTab {
} }
RowLayout { RowLayout {
Layout.row: 8 Layout.row: 9
Layout.column: 0 Layout.column: 0
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.topMargin: 15 Layout.topMargin: 15
@ -211,6 +227,7 @@ MySettingsTab {
MySettingsLabel { MySettingsLabel {
id: promptTemplateLabel id: promptTemplateLabel
text: qsTr("Prompt Template") text: qsTr("Prompt Template")
helpText: qsTr("The template that wraps every prompt")
} }
MySettingsLabel { MySettingsLabel {
id: promptTemplateLabelHelp id: promptTemplateLabelHelp
@ -223,7 +240,7 @@ MySettingsTab {
Rectangle { Rectangle {
id: promptTemplate id: promptTemplate
Layout.row: 9 Layout.row: 10
Layout.column: 0 Layout.column: 0
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
@ -297,34 +314,21 @@ MySettingsTab {
} }
} }
MySettingsLabel {
text: qsTr("Generation Settings")
Layout.row: 10
Layout.column: 0
Layout.columnSpan: 2
Layout.topMargin: 15
Layout.alignment: Qt.AlignHCenter
Layout.minimumWidth: promptTemplate.width
horizontalAlignment: Qt.AlignHCenter
font.pixelSize: theme.fontSizeLarge
font.bold: true
}
GridLayout { GridLayout {
Layout.row: 11 Layout.row: 11
Layout.column: 0 Layout.column: 0
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.topMargin: 15 Layout.topMargin: 15
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumWidth: promptTemplate.width
columns: 4 columns: 4
rowSpacing: 10 rowSpacing: 30
columnSpacing: 10 columnSpacing: 10
MySettingsLabel { MySettingsLabel {
id: contextLengthLabel id: contextLengthLabel
visible: !root.currentModelInfo.isOnline visible: !root.currentModelInfo.isOnline
text: qsTr("Context Length") text: qsTr("Context Length")
helpText: qsTr("Conversation context window")
Layout.row: 0 Layout.row: 0
Layout.column: 0 Layout.column: 0
} }
@ -374,6 +378,7 @@ MySettingsTab {
MySettingsLabel { MySettingsLabel {
id: tempLabel id: tempLabel
text: qsTr("Temperature") text: qsTr("Temperature")
helpText: qsTr("The temperature for model token generation")
Layout.row: 1 Layout.row: 1
Layout.column: 2 Layout.column: 2
} }
@ -418,6 +423,7 @@ MySettingsTab {
MySettingsLabel { MySettingsLabel {
id: topPLabel id: topPLabel
text: qsTr("Top P") text: qsTr("Top P")
helpText: qsTr("Prevents choosing highly unlikely tokens")
Layout.row: 2 Layout.row: 2
Layout.column: 0 Layout.column: 0
} }
@ -461,6 +467,7 @@ MySettingsTab {
MySettingsLabel { MySettingsLabel {
id: minPLabel id: minPLabel
text: qsTr("Min P") text: qsTr("Min P")
helpText: qsTr("Minimum relative probability")
Layout.row: 3 Layout.row: 3
Layout.column: 0 Layout.column: 0
} }
@ -506,6 +513,7 @@ MySettingsTab {
id: topKLabel id: topKLabel
visible: !root.currentModelInfo.isOnline visible: !root.currentModelInfo.isOnline
text: qsTr("Top K") text: qsTr("Top K")
helpText: qsTr("Size of selection pool for tokens")
Layout.row: 2 Layout.row: 2
Layout.column: 2 Layout.column: 2
} }
@ -551,6 +559,7 @@ MySettingsTab {
id: maxLengthLabel id: maxLengthLabel
visible: !root.currentModelInfo.isOnline visible: !root.currentModelInfo.isOnline
text: qsTr("Max Length") text: qsTr("Max Length")
helpText: qsTr("Maximum length of response in tokens")
Layout.row: 0 Layout.row: 0
Layout.column: 2 Layout.column: 2
} }
@ -597,6 +606,7 @@ MySettingsTab {
id: batchSizeLabel id: batchSizeLabel
visible: !root.currentModelInfo.isOnline visible: !root.currentModelInfo.isOnline
text: qsTr("Prompt Batch Size") text: qsTr("Prompt Batch Size")
helpText: qsTr("Amount of prompt tokens to process at once")
Layout.row: 1 Layout.row: 1
Layout.column: 0 Layout.column: 0
} }
@ -642,6 +652,7 @@ MySettingsTab {
id: repeatPenaltyLabel id: repeatPenaltyLabel
visible: !root.currentModelInfo.isOnline visible: !root.currentModelInfo.isOnline
text: qsTr("Repeat Penalty") text: qsTr("Repeat Penalty")
helpText: qsTr("Penalize repetitiveness")
Layout.row: 4 Layout.row: 4
Layout.column: 2 Layout.column: 2
} }
@ -687,6 +698,7 @@ MySettingsTab {
id: repeatPenaltyTokensLabel id: repeatPenaltyTokensLabel
visible: !root.currentModelInfo.isOnline visible: !root.currentModelInfo.isOnline
text: qsTr("Repeat Penalty Tokens") text: qsTr("Repeat Penalty Tokens")
helpText: qsTr("Length to apply penalty")
Layout.row: 3 Layout.row: 3
Layout.column: 2 Layout.column: 2
} }
@ -733,6 +745,7 @@ MySettingsTab {
id: gpuLayersLabel id: gpuLayersLabel
visible: !root.currentModelInfo.isOnline visible: !root.currentModelInfo.isOnline
text: qsTr("GPU Layers") text: qsTr("GPU Layers")
helpText: qsTr("How many GPU layers to load into VRAM")
Layout.row: 4 Layout.row: 4
Layout.column: 0 Layout.column: 0
} }
@ -790,9 +803,8 @@ MySettingsTab {
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.topMargin: 15 Layout.topMargin: 15
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumWidth: promptTemplate.width height: 2
height: 3 color: theme.settingsDivider
color: theme.accentColor
} }
} }
} }

View File

@ -0,0 +1,321 @@
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Dialogs
import QtQuick.Layouts
import chatlistmodel
import download
import llm
import modellist
import network
import mysettings
Rectangle {
id: modelsView
color: theme.viewBackground
signal addModelViewRequested()
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 30
Item {
Layout.fillWidth: true
Layout.fillHeight: true
visible: ModelList.installedModels.count === 0
ColumnLayout {
id: noInstalledLabel
anchors.centerIn: parent
spacing: 0
Text {
Layout.alignment: Qt.AlignCenter
text: qsTr("No Models Installed")
color: theme.mutedLightTextColor
font.pixelSize: theme.fontSizeBannerSmall
}
Text {
Layout.topMargin: 15
horizontalAlignment: Qt.AlignHCenter
color: theme.mutedLighterTextColor
text: qsTr("Install a model to get started using GPT4All")
font.pixelSize: theme.fontSizeLarge
}
}
MyButton {
anchors.top: noInstalledLabel.bottom
anchors.topMargin: 50
anchors.horizontalCenter: noInstalledLabel.horizontalCenter
rightPadding: 60
leftPadding: 60
text: qsTr("\uFF0B Add Model")
onClicked: {
addModelViewRequested()
}
Accessible.role: Accessible.Button
Accessible.name: qsTr("Shows the add model view")
}
}
RowLayout {
visible: ModelList.installedModels.count !== 0
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: 50
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
Layout.minimumWidth: 200
spacing: 5
Text {
id: welcome
text: qsTr("Installed Models")
font.pixelSize: theme.fontSizeBanner
color: theme.titleTextColor
}
Text {
text: qsTr("Locally installed large language models")
font.pixelSize: theme.fontSizeLarge
color: theme.titleInfoTextColor
}
}
Rectangle {
Layout.fillWidth: true
height: 0
}
MyButton {
Layout.alignment: Qt.AlignTop | Qt.AlignRight
text: qsTr("\uFF0B Add Model")
onClicked: {
addModelViewRequested()
}
}
}
ScrollView {
id: scrollView
visible: ModelList.installedModels.count !== 0
ScrollBar.vertical.policy: ScrollBar.AsNeeded
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
ListView {
id: modelListView
model: ModelList.installedModels
boundsBehavior: Flickable.StopAtBounds
spacing: 30
delegate: Rectangle {
id: delegateItem
width: modelListView.width
height: childrenRect.height + 60
color: theme.conversationBackground
radius: 10
border.width: 1
border.color: theme.controlBorder
ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 30
Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
text: name
elide: Text.ElideRight
color: theme.titleTextColor
font.pixelSize: theme.fontSizeLargest
font.bold: true
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Model file")
Accessible.description: qsTr("Model file to be downloaded")
}
Rectangle {
Layout.fillWidth: true
height: 1
color: theme.dividerColor
}
Text {
id: descriptionText
text: description
font.pixelSize: theme.fontSizeLarge
Layout.row: 1
Layout.topMargin: 10
wrapMode: Text.WordWrap
textFormat: Text.StyledText
color: theme.textColor
linkColor: theme.textColor
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Description")
Accessible.description: qsTr("File description")
onLinkActivated: Qt.openUrlExternally(link)
}
Item {
Layout.minimumWidth: childrenRect.width
Layout.minimumHeight: childrenRect.height
Layout.bottomMargin: 10
RowLayout {
id: paramRow
anchors.centerIn: parent
ColumnLayout {
Layout.topMargin: 10
Layout.bottomMargin: 10
Layout.leftMargin: 20
Layout.rightMargin: 20
Text {
text: qsTr("File size")
font.pixelSize: theme.fontSizeSmaller
color: theme.mutedDarkTextColor
}
Text {
text: filesize
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.bold: true
}
}
Rectangle {
width: 1
Layout.fillHeight: true
color: theme.dividerColor
}
ColumnLayout {
Layout.topMargin: 10
Layout.bottomMargin: 10
Layout.leftMargin: 20
Layout.rightMargin: 20
Text {
text: qsTr("RAM required")
font.pixelSize: theme.fontSizeSmaller
color: theme.mutedDarkTextColor
}
Text {
text: ramrequired + qsTr(" GB")
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.bold: true
}
}
Rectangle {
width: 1
Layout.fillHeight: true
color: theme.dividerColor
}
ColumnLayout {
Layout.topMargin: 10
Layout.bottomMargin: 10
Layout.leftMargin: 20
Layout.rightMargin: 20
Text {
text: qsTr("Parameters")
font.pixelSize: theme.fontSizeSmaller
color: theme.mutedDarkTextColor
}
Text {
text: parameters
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.bold: true
}
}
Rectangle {
width: 1
Layout.fillHeight: true
color: theme.dividerColor
}
ColumnLayout {
Layout.topMargin: 10
Layout.bottomMargin: 10
Layout.leftMargin: 20
Layout.rightMargin: 20
Text {
text: qsTr("Quant")
font.pixelSize: theme.fontSizeSmaller
color: theme.mutedDarkTextColor
}
Text {
text: quant
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.bold: true
}
}
Rectangle {
width: 1
Layout.fillHeight: true
color: theme.dividerColor
}
ColumnLayout {
Layout.topMargin: 10
Layout.bottomMargin: 10
Layout.leftMargin: 20
Layout.rightMargin: 20
Text {
text: qsTr("Type")
font.pixelSize: theme.fontSizeSmaller
color: theme.mutedDarkTextColor
}
Text {
text: type
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.bold: true
}
}
}
Rectangle {
color: "transparent"
anchors.fill: paramRow
border.color: theme.dividerColor
border.width: 1
radius: 10
}
}
Rectangle {
Layout.fillWidth: true
height: 1
color: theme.dividerColor
}
RowLayout {
Layout.fillWidth: true
spacing: 30
Layout.leftMargin: 15
Layout.topMargin: 15
Text {
text: qsTr("Remove")
elide: Text.ElideRight
color: theme.red500
font.bold: true
font.pixelSize: theme.fontSizeSmall
TapHandler {
onTapped: {
Download.removeModel(filename);
}
}
}
}
}
}
}
}
}
}

View File

@ -5,16 +5,19 @@ import QtQuick.Controls.Basic
BusyIndicator { BusyIndicator {
id: control id: control
property real size: 48
property color color: theme.accentColor
contentItem: Item { contentItem: Item {
implicitWidth: 48 implicitWidth: control.size
implicitHeight: 48 implicitHeight: control.size
Item { Item {
id: item id: item
x: parent.width / 2 - width / 2 x: parent.width / 2 - width / 2
y: parent.height / 2 - height / 2 y: parent.height / 2 - height / 2
width: 48 width: control.size
height: 48 height: control.size
opacity: control.running ? 1 : 0 opacity: control.running ? 1 : 0
Behavior on opacity { Behavior on opacity {
@ -40,21 +43,21 @@ BusyIndicator {
id: delegate id: delegate
x: item.width / 2 - width / 2 x: item.width / 2 - width / 2
y: item.height / 2 - height / 2 y: item.height / 2 - height / 2
implicitWidth: 10 implicitWidth: control.size * .2
implicitHeight: 10 implicitHeight: control.size * .2
radius: 5 radius: control.size * .1
color: theme.accentColor color: control.color
required property int index required property int index
transform: [ transform: [
Translate { Translate {
y: -Math.min(item.width, item.height) * 0.5 + 5 y: -Math.min(item.width, item.height) * 0.5 + delegate.radius
}, },
Rotation { Rotation {
angle: delegate.index / repeater.count * 360 angle: delegate.index / repeater.count * 360
origin.x: 5 origin.x: delegate.radius
origin.y: 5 origin.y: delegate.radius
} }
] ]
} }

View File

@ -17,11 +17,16 @@ Button {
property real borderWidth: MySettings.chatTheme === "LegacyDark" ? 1 : 0 property real borderWidth: MySettings.chatTheme === "LegacyDark" ? 1 : 0
property color borderColor: theme.buttonBorder property color borderColor: theme.buttonBorder
property real fontPixelSize: theme.fontSizeLarge property real fontPixelSize: theme.fontSizeLarge
property bool fontPixelBold: false
property alias textAlignment: textContent.horizontalAlignment
contentItem: Text { contentItem: Text {
id: textContent
text: myButton.text text: myButton.text
horizontalAlignment: Text.AlignHCenter horizontalAlignment: myButton.textAlignment
color: myButton.enabled ? textColor : mutedTextColor color: myButton.enabled ? textColor : mutedTextColor
font.pixelSize: fontPixelSize font.pixelSize: fontPixelSize
font.bold: fontPixelBold
Accessible.role: Accessible.Button Accessible.role: Accessible.Button
Accessible.name: text Accessible.name: text
} }
@ -29,7 +34,7 @@ Button {
radius: myButton.backgroundRadius radius: myButton.backgroundRadius
border.width: myButton.borderWidth border.width: myButton.borderWidth
border.color: myButton.borderColor border.color: myButton.borderColor
color: myButton.hovered ? backgroundColorHovered : backgroundColor color: !myButton.enabled ? theme.mutedTextColor : myButton.hovered ? backgroundColorHovered : backgroundColor
} }
Accessible.role: Accessible.Button Accessible.role: Accessible.Button
Accessible.name: text Accessible.name: text

View File

@ -73,7 +73,7 @@ ComboBox {
context.moveTo(0, height / 2 + 2); context.moveTo(0, height / 2 + 2);
context.lineTo(width / 2, height); context.lineTo(width / 2, height);
context.lineTo(width, height / 2 + 2); context.lineTo(width, height / 2 + 2);
context.strokeStyle = comboBox.pressed ? theme.gray400 : theme.gray300; context.strokeStyle = comboBox.pressed ? theme.mutedLightTextColor : theme.mutedLighterTextColor;
context.stroke(); context.stroke();
} }

View File

@ -12,6 +12,8 @@ TextField {
background: Rectangle { background: Rectangle {
implicitWidth: 150 implicitWidth: 150
color: theme.controlBackground color: theme.controlBackground
border.width: 1
border.color: theme.controlBorder
radius: 10 radius: 10
} }
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval

View File

@ -0,0 +1,44 @@
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import Qt5Compat.GraphicalEffects
import mysettings
MyButton {
id: fancyLink
property alias imageSource: myimage.source
Image {
id: myimage
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 12
sourceSize: Qt.size(15, 15)
mipmap: true
visible: false
}
ColorOverlay {
anchors.fill: myimage
source: myimage
color: fancyLink.hovered ? theme.fancyLinkTextHovered : theme.fancyLinkText
}
borderWidth: 0
backgroundColor: "transparent"
backgroundColorHovered: "transparent"
fontPixelBold: true
leftPadding: 35
rightPadding: 8
topPadding: 1
bottomPadding: 1
textColor: fancyLink.hovered ? theme.fancyLinkTextHovered : theme.fancyLinkText
fontPixelSize: theme.fontSizeSmall
background: Rectangle {
color: "transparent"
}
Accessible.name: qsTr("Fancy link")
Accessible.description: qsTr("A stylized link")
}

View File

@ -31,9 +31,10 @@ Button {
Image { Image {
id: image id: image
anchors.centerIn: parent anchors.centerIn: parent
visible: false
mipmap: true mipmap: true
width: 20 sourceSize.width: 20
height: 20 sourceSize.height: 20
} }
ColorOverlay { ColorOverlay {
anchors.fill: image anchors.fill: image

View File

@ -9,10 +9,10 @@ Button {
padding: 10 padding: 10
rightPadding: 18 rightPadding: 18
leftPadding: 18 leftPadding: 18
property color textColor: MySettings.chatTheme === "Dark" ? theme.green800 : theme.green600 property color textColor: theme.lightButtonText
property color mutedTextColor: textColor property color mutedTextColor: theme.lightButtonMutedText
property color backgroundColor: MySettings.chatTheme === "Dark" ? theme.green400 : theme.green200 property color backgroundColor: theme.lightButtonBackground
property color backgroundColorHovered: theme.green300 property color backgroundColorHovered: enabled ? theme.lightButtonBackgroundHovered : backgroundColor
property real borderWidth: 0 property real borderWidth: 0
property color borderColor: "transparent" property color borderColor: "transparent"
property real fontPixelSize: theme.fontSizeLarge property real fontPixelSize: theme.fontSizeLarge

View File

@ -10,13 +10,13 @@ Button {
rightPadding: 18 rightPadding: 18
leftPadding: 18 leftPadding: 18
font.pixelSize: theme.fontSizeLarge font.pixelSize: theme.fontSizeLarge
property color textColor: MySettings.chatTheme === "Dark" ? theme.red800 : theme.red600 property color textColor: theme.darkButtonText
property color mutedTextColor: MySettings.chatTheme === "Dark" ? theme.red400 : theme.red300 property color mutedTextColor: theme.darkButtonMutedText
property color backgroundColor: enabled ? (MySettings.chatTheme === "Dark" ? theme.red400 : theme.red200) : property color backgroundColor: theme.darkButtonBackground
(MySettings.chatTheme === "Dark" ? theme.red200 : theme.red100) property color backgroundColorHovered: enabled ? theme.darkButtonBackgroundHovered : backgroundColor
property color backgroundColorHovered: enabled ? (MySettings.chatTheme === "Dark" ? theme.red500 : theme.red300) : backgroundColor
property real borderWidth: 0 property real borderWidth: 0
property color borderColor: "transparent" property color borderColor: "transparent"
contentItem: Text { contentItem: Text {
text: myButton.text text: myButton.text
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter

View File

@ -2,9 +2,40 @@ import QtCore
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.Basic import QtQuick.Controls.Basic
import QtQuick.Layouts
Label { ColumnLayout {
color: theme.settingsTitleTextColor id: root
font.pixelSize: theme.fontSizeSmall property alias text: mainTextLabel.text
font.bold: true property alias helpText: helpTextLabel.text
property alias textFormat: mainTextLabel.textFormat
property alias wrapMode: mainTextLabel.wrapMode
property alias font: mainTextLabel.font
property alias horizontalAlignment: mainTextLabel.horizontalAlignment
signal linkActivated(link : url);
property alias color: mainTextLabel.color
property alias linkColor: mainTextLabel.linkColor
Label {
id: mainTextLabel
color: theme.settingsTitleTextColor
font.pixelSize: theme.fontSizeSmall
font.bold: true
onLinkActivated: function(link) {
root.linkActivated(link);
}
}
Label {
id: helpTextLabel
Layout.fillWidth: true
wrapMode: Text.Wrap
color: theme.settingsTitleTextColor
text: mainTextLabel.text
font.pixelSize: theme.fontSizeSmaller
font.bold: false
onLinkActivated: function(link) {
root.linkActivated(link);
}
}
} }

View File

@ -14,44 +14,12 @@ Item {
id: theme id: theme
} }
property alias title: titleLabelText.text
property ListModel tabTitlesModel: ListModel { } property ListModel tabTitlesModel: ListModel { }
property list<Component> tabs: [ ] property list<Component> tabs: [ ]
Rectangle {
id: titleLabel
anchors.top: parent.top
anchors.leftMargin: 20
anchors.rightMargin: 15
anchors.left: parent.left
anchors.right: parent.right
height: titleLabelText.height
color: "transparent"
Label {
id: titleLabelText
anchors.left: parent.left
color: theme.titleTextColor
topPadding: 10
bottomPadding: 10
font.pixelSize: theme.fontSizeLargest
font.bold: true
}
}
Rectangle {
anchors.top: titleLabel.bottom
anchors.leftMargin: 20
anchors.rightMargin: 15
anchors.left: parent.left
anchors.right: parent.right
height: 3
color: theme.accentColor
}
TabBar { TabBar {
id: settingsTabBar id: settingsTabBar
anchors.top: titleLabel.bottom anchors.top: parent.top
anchors.topMargin: 15
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
width: parent.width / 1.75 width: parent.width / 1.75
z: 200 z: 200
@ -89,8 +57,8 @@ Item {
anchors.rightMargin: 15 anchors.rightMargin: 15
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
height: 3 height: 2
color: theme.accentColor color: theme.settingsDivider
} }
FolderDialog { FolderDialog {
@ -106,7 +74,8 @@ Item {
StackLayout { StackLayout {
id: stackLayout id: stackLayout
anchors.top: tabTitlesModel.count > 1 ? dividerTabBar.bottom : titleLabel.bottom anchors.top: tabTitlesModel.count > 1 ? dividerTabBar.bottom : parent.top
anchors.topMargin: 5
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom

View File

@ -8,12 +8,9 @@ Item {
id: root id: root
property string title: "" property string title: ""
property Item contentItem: null property Item contentItem: null
property Item advancedSettings: null
property bool showAdvancedSettingsButton: true
property bool showRestoreDefaultsButton: true property bool showRestoreDefaultsButton: true
property var openFolderDialog property var openFolderDialog
signal restoreDefaultsClicked signal restoreDefaultsClicked
signal downloadClicked
onContentItemChanged: function() { onContentItemChanged: function() {
if (contentItem) { if (contentItem) {
@ -23,14 +20,6 @@ Item {
} }
} }
onAdvancedSettingsChanged: function() {
if (advancedSettings) {
advancedSettings.parent = advancedInner;
advancedSettings.anchors.left = advancedInner.left;
advancedSettings.anchors.right = advancedInner.right;
}
}
ScrollView { ScrollView {
id: scrollView id: scrollView
width: parent.width width: parent.width
@ -61,14 +50,9 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Column {
id: advancedInner
visible: false
Layout.fillWidth: true
}
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 20
height: restoreDefaultsButton.height height: restoreDefaultsButton.height
MySettingsButton { MySettingsButton {
id: restoreDefaultsButton id: restoreDefaultsButton
@ -84,20 +68,6 @@ Item {
root.restoreDefaultsClicked(); root.restoreDefaultsClicked();
} }
} }
MySettingsButton {
id: advancedSettingsButton
anchors.right: parent.right
visible: root.advancedSettings && showAdvancedSettingsButton
width: implicitWidth
text: !advancedInner.visible ? qsTr("Advanced Settings") : qsTr("Hide Advanced Settings")
font.pixelSize: theme.fontSizeLarge
Accessible.role: Accessible.Button
Accessible.name: text
Accessible.description: qsTr("Shows/hides the advanced settings")
onClicked: {
advancedInner.visible = !advancedInner.visible;
}
}
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More