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
url = https://github.com/nomic-ai/llama.cpp.git
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
Dlhandle::Dlhandle(const fs::path &fpath) {
Dlhandle::Dlhandle(const fs::path &fpath)
{
chandle = dlopen(fpath.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!chandle) {
throw Exception("dlopen: "s + dlerror());
}
}
Dlhandle::~Dlhandle() {
Dlhandle::~Dlhandle()
{
if (chandle) dlclose(chandle);
}
void *Dlhandle::get_internal(const char *symbol) const {
void *Dlhandle::get_internal(const char *symbol) const
{
return dlsym(chandle, symbol);
}
#else // defined(_WIN32)
Dlhandle::Dlhandle(const fs::path &fpath) {
Dlhandle::Dlhandle(const fs::path &fpath)
{
fs::path afpath = fs::absolute(fpath);
// 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));
}
void *Dlhandle::get_internal(const char *symbol) const {
void *Dlhandle::get_internal(const char *symbol) const
{
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
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());
if(mem_req != nullptr) {
*mem_req = 0;
@ -667,7 +668,8 @@ GPTJ::GPTJ()
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)ngl;
gptj_model dummy_model;
@ -677,7 +679,8 @@ size_t GPTJ::requiredMem(const std::string &modelPath, int n_ctx, int ngl) {
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)ngl;
d_ptr->modelLoaded = false;
@ -698,7 +701,8 @@ bool GPTJ::loadModel(const std::string &modelPath, int n_ctx, int ngl) {
return true;
}
void GPTJ::setThreadCount(int32_t n_threads) {
void GPTJ::setThreadCount(int32_t n_threads)
{
d_ptr->n_threads = n_threads;
}
@ -780,7 +784,8 @@ const std::vector<LLModel::Token> &GPTJ::endTokens() const
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");
if (kid == -1)
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
extern "C" {
DLL_EXPORT bool is_g4a_backend_model_implementation() {
DLL_EXPORT bool is_g4a_backend_model_implementation()
{
return true;
}
DLL_EXPORT const char *get_model_type() {
DLL_EXPORT const char *get_model_type()
{
return modelType_;
}
DLL_EXPORT const char *get_build_variant() {
DLL_EXPORT const char *get_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 gguf_init_params params = {
/*.no_alloc = */ true,
@ -832,11 +841,13 @@ DLL_EXPORT char *get_file_arch(const char *fname) {
return arch;
}
DLL_EXPORT bool is_arch_supported(const char *arch) {
DLL_EXPORT bool is_arch_supported(const char *arch)
{
return !strcmp(arch, "gptj");
}
DLL_EXPORT LLModel *construct() {
DLL_EXPORT LLModel *construct()
{
return new GPTJ;
}
}

View File

@ -84,16 +84,19 @@ static const std::vector<const char *> EMBEDDING_ARCHES {
"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();
}
static bool llama_verbose() {
static bool llama_verbose()
{
const char* var = getenv("GPT4ALL_VERBOSE_LLAMACPP");
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;
if (llama_verbose() || level <= GGML_LOG_LEVEL_ERROR) {
fputs(text, stderr);
@ -147,7 +150,8 @@ static int llama_sample_top_p_top_k(
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");
if (kid == -1)
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);
}
static gguf_context *load_gguf(const char *fname) {
static gguf_context *load_gguf(const char *fname)
{
struct gguf_init_params params = {
/*.no_alloc = */ true,
/*.ctx = */ nullptr,
@ -180,7 +185,8 @@ static gguf_context *load_gguf(const char *fname) {
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;
std::string arch;
@ -237,7 +243,8 @@ struct llama_file_hparams {
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
(void)ngl; // FIXME(cetenzzre): use this value
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;
}
bool LLamaModel::isModelBlacklisted(const std::string &modelPath) const {
bool LLamaModel::isModelBlacklisted(const std::string &modelPath) const
{
auto * ctx = load_gguf(modelPath.c_str());
if (!ctx) {
std::cerr << __func__ << ": failed to load " << modelPath << "\n";
@ -297,7 +305,8 @@ bool LLamaModel::isModelBlacklisted(const std::string &modelPath) const {
return res;
}
bool LLamaModel::isEmbeddingModel(const std::string &modelPath) const {
bool LLamaModel::isEmbeddingModel(const std::string &modelPath) const
{
bool result = false;
std::string arch;
@ -453,12 +462,14 @@ bool LLamaModel::loadModel(const std::string &modelPath, int n_ctx, int ngl)
return true;
}
void LLamaModel::setThreadCount(int32_t n_threads) {
void LLamaModel::setThreadCount(int32_t n_threads)
{
d_ptr->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;
}
@ -581,7 +592,8 @@ int32_t LLamaModel::layerCount(std::string const &modelPath) const
}
#ifdef GGML_USE_VULKAN
static const char *getVulkanVendorName(uint32_t vendorID) {
static const char *getVulkanVendorName(uint32_t vendorID)
{
switch (vendorID) {
case 0x10DE: return "nvidia";
case 0x1002: return "amd";
@ -738,11 +750,13 @@ bool LLamaModel::usingGPUDevice() const
return hasDevice;
}
const char *LLamaModel::backendName() const {
const char *LLamaModel::backendName() const
{
return d_ptr->backend_name;
}
const char *LLamaModel::gpuDeviceName() const {
const char *LLamaModel::gpuDeviceName() const
{
if (usingGPUDevice()) {
#if defined(GGML_USE_KOMPUTE) || defined(GGML_USE_VULKAN) || defined(GGML_USE_CUDA)
return d_ptr->deviceName.c_str();
@ -768,13 +782,15 @@ void llama_batch_add(
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++) {
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);
}
@ -894,12 +910,14 @@ void LLamaModel::embed(
// MD5 hash of "nomic empty"
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; };
}
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));
return 1.0 / std::max(magnitude, 1e-12);
}
@ -1107,19 +1125,23 @@ void LLamaModel::embedInternal(
#endif
extern "C" {
DLL_EXPORT bool is_g4a_backend_model_implementation() {
DLL_EXPORT bool is_g4a_backend_model_implementation()
{
return true;
}
DLL_EXPORT const char *get_model_type() {
DLL_EXPORT const char *get_model_type()
{
return modelType_;
}
DLL_EXPORT const char *get_build_variant() {
DLL_EXPORT const char *get_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;
std::string archStr;
@ -1144,11 +1166,13 @@ cleanup:
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();
}
DLL_EXPORT LLModel *construct() {
DLL_EXPORT LLModel *construct()
{
llama_log_set(llama_log_callback, nullptr);
return new LLamaModel;
}

View File

@ -92,17 +92,20 @@ LLModel::Implementation::Implementation(Implementation &&o)
o.m_dlhandle = nullptr;
}
LLModel::Implementation::~Implementation() {
LLModel::Implementation::~Implementation()
{
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");
}
// 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.
static void addCudaSearchPath() {
static void addCudaSearchPath()
{
#ifdef _WIN32
if (const auto *cudaPath = _wgetenv(L"CUDA_PATH")) {
auto libDir = std::wstring(cudaPath) + L"\\bin";
@ -114,7 +117,8 @@ static void addCudaSearchPath() {
#endif
}
const std::vector<LLModel::Implementation> &LLModel::Implementation::implementationList() {
const std::vector<LLModel::Implementation> &LLModel::Implementation::implementationList()
{
if (cpu_supports_avx() == 0) {
throw std::runtime_error("CPU does not support AVX");
}
@ -169,14 +173,16 @@ const std::vector<LLModel::Implementation> &LLModel::Implementation::implementat
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) {
return buildVariant + "-avxonly";
}
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;
std::optional<std::string> archName;
for (const auto& i : implementationList()) {
@ -200,7 +206,8 @@ const LLModel::Implementation* LLModel::Implementation::implementation(const cha
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;
if (backend != "auto") {
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);
}
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;
const std::vector<Implementation> *impls;
@ -284,7 +292,8 @@ LLModel *LLModel::Implementation::constructGlobalLlama(const std::optional<std::
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;
#ifndef __APPLE__
static const std::string backends[] = {"kompute", "cuda"};
@ -299,33 +308,40 @@ std::vector<LLModel::GPUDevice> LLModel::Implementation::availableGPUDevices(siz
return devices;
}
int32_t LLModel::Implementation::maxContextLength(const std::string &modelPath) {
int32_t LLModel::Implementation::maxContextLength(const std::string &modelPath)
{
auto *llama = constructGlobalLlama();
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();
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();
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;
}
const std::string& LLModel::Implementation::implementationsSearchPath() {
const std::string& LLModel::Implementation::implementationsSearchPath()
{
return s_implementations_search_path;
}
bool LLModel::Implementation::hasSupportedCPU() {
bool LLModel::Implementation::hasSupportedCPU()
{
return cpu_supports_avx() != 0;
}
int LLModel::Implementation::cpuSupportsAVX2() {
int LLModel::Implementation::cpuSupportsAVX2()
{
return cpu_supports_avx2();
}

View File

@ -20,7 +20,8 @@ struct LLModelWrapper {
~LLModelWrapper() { delete llModel; }
};
llmodel_model llmodel_model_create(const char *model_path) {
llmodel_model llmodel_model_create(const char *model_path)
{
const char *error;
auto fres = llmodel_model_create2(model_path, "auto", &error);
if (!fres) {
@ -29,7 +30,8 @@ llmodel_model llmodel_model_create(const char *model_path) {
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;
if (errptr) {
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;
try {
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;
}
void llmodel_model_destroy(llmodel_model model) {
void llmodel_model_destroy(llmodel_model model)
{
delete static_cast<LLModelWrapper *>(model);
}

View File

@ -14,7 +14,8 @@
#include <vector>
// 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();
const int32_t n_discard = (promptCtx.n_ctx - n_keep) * promptCtx.contextErase;
@ -43,7 +44,8 @@ stop_generating:
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]))");
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);
if (plan.work_size > 0) {
buf.resize(plan.work_size);

View File

@ -8,7 +8,8 @@
#include <regex>
#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;
while ((pos = str.find(needle, pos)) != std::string::npos) {
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;
// read file into string
@ -107,7 +109,8 @@ std::map<std::string, int32_t> json_parse(const std::string & fname) {
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;
// 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;
}
std::string regex_escape(const std::string &s) {
std::string regex_escape(const std::string &s)
{
static const std::regex metacharacters(R"([\.\^\$\-\+\(\)\[\]\{\}\|\?\*])");
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
if (!vocab.special_tokens.empty()) {
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());
vocab.token_to_id = ::json_parse(fname);

View File

@ -14,7 +14,8 @@
//
// 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;
}

View File

@ -16,15 +16,16 @@ if(APPLE)
endif()
endif()
set(APP_VERSION_MAJOR 2)
set(APP_VERSION_MINOR 8)
set(APP_VERSION_PATCH 1)
set(APP_VERSION "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}")
set(APP_VERSION_MAJOR 3)
set(APP_VERSION_MINOR 0)
set(APP_VERSION_PATCH 0)
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_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_AUTORCC ON)
@ -91,7 +92,6 @@ qt_add_executable(chat
chatmodel.h chatlistmodel.h chatlistmodel.cpp
chatapi.h chatapi.cpp
database.h database.cpp
embeddings.h embeddings.cpp
download.h download.cpp
embllm.cpp embllm.h
localdocs.h localdocs.cpp localdocsmodel.h localdocsmodel.cpp
@ -102,6 +102,7 @@ qt_add_executable(chat
server.h server.cpp
logger.h logger.cpp
responsetext.h responsetext.cpp
oscompat.h oscompat.cpp
${METAL_SHADER_FILE}
${APP_ICON_RESOURCE}
)
@ -112,21 +113,24 @@ qt_add_qml_module(chat
NO_CACHEGEN
QML_FILES
main.qml
qml/AddCollectionView.qml
qml/AddModelView.qml
qml/ChatDrawer.qml
qml/ChatView.qml
qml/CollectionsDialog.qml
qml/ModelDownloaderDialog.qml
qml/CollectionsDrawer.qml
qml/HomeView.qml
qml/ModelsView.qml
qml/NetworkDialog.qml
qml/NewVersionDialog.qml
qml/ThumbsDownDialog.qml
qml/SettingsDialog.qml
qml/SettingsView.qml
qml/StartupDialog.qml
qml/PopupDialog.qml
qml/AboutDialog.qml
qml/Theme.qml
qml/ModelSettings.qml
qml/ApplicationSettings.qml
qml/LocalDocsSettings.qml
qml/LocalDocsView.qml
qml/SwitchModelDialog.qml
qml/MySettingsTab.qml
qml/MySettingsStack.qml
@ -138,33 +142,58 @@ qt_add_qml_module(chat
qml/MyComboBox.qml
qml/MyDialog.qml
qml/MyDirectoryField.qml
qml/MyFancyLink.qml
qml/MyTextArea.qml
qml/MyTextField.qml
qml/MyCheckBox.qml
qml/MyBusyIndicator.qml
qml/MyMiniButton.qml
qml/MyToolButton.qml
qml/MyWelcomeButton.qml
RESOURCES
icons/antenna_1.svg
icons/antenna_2.svg
icons/antenna_3.svg
icons/send_message.svg
icons/stop_generating.svg
icons/regenerate.svg
icons/chat.svg
icons/changelog.svg
icons/close.svg
icons/copy.svg
icons/db.svg
icons/discord.svg
icons/download.svg
icons/settings.svg
icons/eject.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/info.svg
icons/local-docs.svg
icons/models.svg
icons/nomic_logo.svg
icons/notes.svg
icons/search.svg
icons/trash.svg
icons/network.svg
icons/thumbs_up.svg
icons/thumbs_down.svg
icons/twitter.svg
icons/left_panel_closed.svg
icons/left_panel_open.svg
icons/logo.svg
icons/logo-32.png
icons/logo-48.png
icons/you.svg
icons/alt_logo.svg
)
set_target_properties(chat PROPERTIES
@ -190,6 +219,13 @@ endif()
target_compile_definitions(chat
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)
target_link_libraries(chat
PRIVATE Qt6::Quick Qt6::Svg Qt6::HttpServer Qt6::Sql Qt6::Pdf Qt6::WaylandCompositor)
@ -200,6 +236,20 @@ endif()
target_link_libraries(chat
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})
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
@ -264,6 +314,10 @@ if (LLMODEL_CUDA)
endif()
endif()
install(FILES "${CMAKE_BINARY_DIR}/resources/${LOCAL_EMBEDDING_MODEL}"
DESTINATION resources
COMPONENT ${COMPONENT_NAME_MAIN})
set(CPACK_GENERATOR "IFW")
set(CPACK_VERBATIM_VARIABLES YES)
set(CPACK_IFW_VERBOSE ON)

View File

@ -15,6 +15,7 @@
#include <QTextStream>
#include <Qt>
#include <QtGlobal>
#include <QtLogging>
#include <utility>
@ -130,7 +131,7 @@ void Chat::prompt(const QString &prompt)
void Chat::regenerateResponse()
{
const int index = m_chatModel->count() - 1;
m_chatModel->updateReferences(index, QString(), QList<QString>());
m_chatModel->updateSources(index, QList<ResultInfo>());
emit regenerateResponseRequested();
}
@ -193,43 +194,6 @@ void Chat::responseStopped(qint64 promptResponseMs)
{
m_tokenSpeed = QString();
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();
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
m_generatedName = name.simplified();
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(' ');
emit nameChanged();
}
@ -378,6 +342,8 @@ void Chat::handleFallbackReasonChanged(const QString &fallbackReason)
void Chat::handleDatabaseResultsChanged(const QList<ResultInfo> &results)
{
m_databaseResults = results;
const int index = m_chatModel->count() - 1;
m_chatModel->updateSources(index, m_databaseResults);
}
void Chat::handleModelInfoChanged(const ModelInfo &modelInfo)
@ -389,7 +355,8 @@ void Chat::handleModelInfoChanged(const ModelInfo &modelInfo)
emit modelInfoChanged();
}
void Chat::handleTrySwitchContextOfLoadedModelCompleted(int value) {
void Chat::handleTrySwitchContextOfLoadedModelCompleted(int value)
{
m_trySwitchContextInProgress = value;
emit trySwitchContextInProgressChanged();
}

View File

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

View File

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

View File

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

View File

@ -56,7 +56,8 @@ public:
enum Roles {
IdRole = Qt::UserRole + 1,
NameRole
NameRole,
SectionRole
};
int rowCount(const QModelIndex &parent = QModelIndex()) const override
@ -76,6 +77,26 @@ public:
return item->id();
case NameRole:
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();
@ -86,6 +107,7 @@ public:
QHash<int, QByteArray> roles;
roles[IdRole] = "id";
roles[NameRole] = "name";
roles[SectionRole] = "section";
return roles;
}

View File

@ -16,6 +16,7 @@
#include <QMutex>
#include <QMutexLocker>
#include <QSet>
#include <QStringList>
#include <QVariantMap>
#include <QWaitCondition>
#include <Qt>
@ -28,9 +29,12 @@
#include <functional>
#include <limits>
#include <optional>
#include <string_view>
#include <utility>
#include <vector>
using namespace Qt::Literals::StringLiterals;
//#define DEBUG
//#define DEBUG_MODEL_LOADING
@ -180,7 +184,7 @@ bool ChatLLM::loadDefaultModel()
{
ModelInfo defaultModel = ModelList::globalInstance()->defaultModelInfo();
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 loadModel(defaultModel);
@ -292,7 +296,7 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
setModelInfo(modelInfo);
Q_ASSERT(!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
processSystemPrompt();
return true;
@ -377,9 +381,9 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
static QSet<QString> warned;
auto fname = modelInfo.filename();
if (!warned.contains(fname)) {
emit modelLoadingWarning(QString(
"%1 is known to be broken. Please get a replacement via the download dialog."
).arg(fname));
emit modelLoadingWarning(
u"%1 is known to be broken. Please get a replacement via the download dialog."_s.arg(fname)
);
warned.insert(fname); // don't warn again until restart
}
}
@ -485,7 +489,7 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_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");
} else {
switch (m_llModelInfo.model->implementation().modelType()[0]) {
@ -497,7 +501,7 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_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)
LLModelStore::globalInstance()->releaseModel(std::move(m_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)
@ -527,7 +531,7 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo)); // release back into the store
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) {
@ -542,7 +546,8 @@ bool ChatLLM::isModelLoaded() const
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) {
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());
}
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) {
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
QList<QString> docsContext;
if (!databaseResults.isEmpty())
docsContext.append("### Context:");
for (const ResultInfo &info : databaseResults)
docsContext.append(info.text);
QString docsContext;
if (!databaseResults.isEmpty()) {
QStringList results;
for (const ResultInfo &info : databaseResults)
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();
@ -738,7 +748,7 @@ bool ChatLLM::promptInternal(const QList<QString> &collectionList, const QString
m_timer->start();
if (!docsContext.isEmpty()) {
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_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 recalcFunc = std::bind(&ChatLLM::handleNameRecalculate, this, std::placeholders::_1);
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);
std::string trimmed = trim_whitespace(m_nameResponse);
if (trimmed != m_nameResponse) {

View File

@ -22,6 +22,8 @@
#include <memory>
#include <string>
using namespace Qt::Literals::StringLiterals;
class QDataStream;
enum LLModelType {
@ -68,7 +70,7 @@ private Q_SLOTS:
void handleTimeout()
{
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:

View File

@ -1,6 +1,8 @@
#ifndef CHATMODEL_H
#define CHATMODEL_H
#include "database.h"
#include <QAbstractListModel>
#include <QByteArray>
#include <QDataStream>
@ -26,17 +28,18 @@ struct ChatItem
Q_PROPERTY(bool stopped MEMBER stopped)
Q_PROPERTY(bool thumbsUpState MEMBER thumbsUpState)
Q_PROPERTY(bool thumbsDownState MEMBER thumbsDownState)
Q_PROPERTY(QString references MEMBER references)
Q_PROPERTY(QList<QString> referencesContext MEMBER referencesContext)
Q_PROPERTY(QList<ResultInfo> sources MEMBER sources)
Q_PROPERTY(QList<ResultInfo> consolidatedSources MEMBER consolidatedSources)
public:
// TODO: Maybe we should include the model name here as well as timestamp?
int id = 0;
QString name;
QString value;
QString prompt;
QString newResponse;
QString references;
QList<QString> referencesContext;
QList<ResultInfo> sources;
QList<ResultInfo> consolidatedSources;
bool currentResponse = false;
bool stopped = false;
bool thumbsUpState = false;
@ -62,8 +65,8 @@ public:
StoppedRole,
ThumbsUpStateRole,
ThumbsDownStateRole,
ReferencesRole,
ReferencesContextRole
SourcesRole,
ConsolidatedSourcesRole
};
int rowCount(const QModelIndex &parent = QModelIndex()) const override
@ -97,10 +100,10 @@ public:
return item.thumbsUpState;
case ThumbsDownStateRole:
return item.thumbsDownState;
case ReferencesRole:
return item.references;
case ReferencesContextRole:
return item.referencesContext;
case SourcesRole:
return QVariant::fromValue(item.sources);
case ConsolidatedSourcesRole:
return QVariant::fromValue(item.consolidatedSources);
}
return QVariant();
@ -118,8 +121,8 @@ public:
roles[StoppedRole] = "stopped";
roles[ThumbsUpStateRole] = "thumbsUpState";
roles[ThumbsDownStateRole] = "thumbsDownState";
roles[ReferencesRole] = "references";
roles[ReferencesContextRole] = "referencesContext";
roles[SourcesRole] = "sources";
roles[ConsolidatedSourcesRole] = "consolidatedSources";
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;
ChatItem &item = m_chatItems[index];
if (item.references != references) {
item.references = references;
emit dataChanged(createIndex(index, 0), createIndex(index, 0), {ReferencesRole});
}
if (item.referencesContext != referencesContext) {
item.referencesContext = referencesContext;
emit dataChanged(createIndex(index, 0), createIndex(index, 0), {ReferencesContextRole});
}
item.sources = sources;
item.consolidatedSources = consolidateSources(sources);
emit dataChanged(createIndex(index, 0), createIndex(index, 0), {SourcesRole});
emit dataChanged(createIndex(index, 0), createIndex(index, 0), {ConsolidatedSourcesRole});
}
Q_INVOKABLE void updateThumbsUpState(int index, bool b)
@ -259,9 +271,56 @@ public:
stream << c.stopped;
stream << c.thumbsUpState;
stream << c.thumbsDownState;
if (version > 2) {
stream << c.references;
stream << c.referencesContext;
if (version > 7) {
stream << c.sources.size();
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;
@ -282,9 +341,109 @@ public:
stream >> c.stopped;
stream >> c.thumbsUpState;
stream >> c.thumbsDownState;
if (version > 2) {
stream >> c.references;
stream >> c.referencesContext;
if (version > 7) {
qsizetype count;
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());
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 <QElapsedTimer>
#include <QDateTime>
#include <QFileInfo>
#include <QHash>
#include <QLatin1String>
#include <QList>
#include <QMap>
#include <QObject>
#include <QQueue>
#include <QSet>
#include <QSqlDatabase>
#include <QString>
#include <QStringList>
#include <QThread>
#include <QVector>
#include <QtGlobal>
#include <QtSql>
#include <cstddef>
class EmbeddingLLM;
class Embeddings;
using namespace Qt::Literals::StringLiterals;
class QFileSystemWatcher;
class QSqlError;
class QTextStream;
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
{
int folder;
@ -33,34 +44,82 @@ struct DocumentInfo
size_t currentPosition = 0;
bool currentlyProcessing = false;
bool isPdf() const {
return doc.suffix() == QLatin1String("pdf");
return doc.suffix() == u"pdf"_s;
}
};
struct ResultInfo {
QString file; // [Required] The name of the file, but not the full path
QString title; // [Optional] The title of the document
QString author; // [Optional] The author of the document
QString date; // [Required] The creation or the last modification date whichever is latest
QString text; // [Required] The text actually used in the augmented context
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
QString collection; // [Required] The name of the collection
QString path; // [Required] The full path
QString file; // [Required] The name of the file, but not the full path
QString title; // [Optional] The title of the document
QString author; // [Optional] The author of the document
QString date; // [Required] The creation or the last modification date whichever is latest
QString text; // [Required] The text actually used in the augmented context
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 {
// -- Fields persisted to database --
int collection_id = -1;
int folder_id = -1;
QString collection;
QString folder_path;
int folder_id = -1;
QString embeddingModel;
// -- Transient fields --
bool installed = false;
bool indexing = false;
bool forceIndexing = false;
QString error;
// progress
int currentDocsToIndex = 0;
int totalDocsToIndex = 0;
size_t currentBytesToIndex = 0;
size_t totalBytesToIndex = 0;
size_t currentEmbeddingsToIndex = 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)
@ -68,53 +127,55 @@ class Database : public QObject
{
Q_OBJECT
public:
Database(int chunkSize);
virtual ~Database();
Database(int chunkSize, QStringList extensions);
~Database() override;
bool isValid() const { return m_databaseValid; }
public Q_SLOTS:
void start();
void scanQueue();
void scanDocuments(int folder_id, const QString &folder_path, bool isNew);
bool addFolder(const QString &collection, const QString &path, bool fromDb);
void scanQueueBatch();
void scanDocuments(int folder_id, const QString &folder_path);
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 retrieveFromDB(const QList<QString> &collections, const QString &text, int retrievalSize, QList<ResultInfo> *results);
void cleanDB();
void changeChunkSize(int chunkSize);
void changeFileExtensions(const QStringList &extensions);
Q_SIGNALS:
void docsToScanChanged();
void updateInstalled(int folder_id, bool b);
void updateIndexing(int folder_id, bool b);
void updateError(int folder_id, const QString &error);
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 collectionListUpdated(const QList<CollectionItem> &collectionList);
// Signals for the gui only
void requestUpdateGuiForCollectionItem(const CollectionItem &item);
void requestAddGuiCollectionItem(const CollectionItem &item);
void requestRemoveGuiFolderById(const QString &collection, int folder_id);
void requestGuiCollectionListUpdated(const QList<CollectionItem> &collectionList);
void databaseValidChanged();
private Q_SLOTS:
void directoryChanged(const QString &path);
bool addFolderToWatch(const QString &path);
bool removeFolderFromWatch(const QString &path);
int addCurrentFolders();
void addCurrentFolders();
void handleEmbeddingsGenerated(const QVector<EmbeddingResult> &embeddings);
void handleErrorGenerated(int folder_id, const QString &error);
void handleErrorGenerated(const QVector<EmbeddingChunk> &chunks, const QString &error);
private:
enum class FolderStatus { Started, Embedding, Complete };
struct FolderStatusRecord { qint64 startTime; bool isNew; int numDocs, docsChanged, chunksRead; };
void transaction();
void commit();
void rollback();
void removeFolderInternal(const QString &collection, int folder_id, const QString &path);
size_t chunkStream(QTextStream &stream, int folder_id, int document_id, const QString &file,
const QString &title, const QString &author, const QString &subject, const QString &keywords, int page,
int maxChunks = -1);
void removeEmbeddingsByDocumentId(int document_id);
void scheduleNext(int folder_id, size_t countForFolder);
bool hasContent();
// not found -> 0, , exists and has content -> 1, error -> -1
int openDatabase(const QString &modelPath, bool create = true, int ver = LOCALDOCS_VERSION);
bool openLatestDb(const QString &modelPath, QList<CollectionItem> &oldCollections);
bool initDb(const QString &modelPath, const QList<CollectionItem> &oldCollections);
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,
int document_id, const QString &document_path, const QSqlError &error);
size_t countOfDocuments(int folder_id) const;
@ -123,20 +184,37 @@ private:
void removeFolderFromDocumentQueue(int folder_id);
void enqueueDocumentInternal(const DocumentInfo &info, bool prepend = false);
void enqueueDocuments(int folder_id, const QVector<DocumentInfo> &infos);
void updateIndexingStatus();
void updateFolderStatus(int folder_id, FolderStatus status, int numDocs = -1, bool atStart = false, bool isNew = false);
void scanQueue();
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:
QSqlDatabase m_db;
int m_chunkSize;
QStringList m_scannedFileExtensions;
QTimer *m_scanTimer;
QMap<int, QQueue<DocumentInfo>> m_docsToScan;
QElapsedTimer m_indexingTimer;
QMap<int, FolderStatusRecord> m_foldersBeingIndexed;
QList<ResultInfo> m_retrieve;
QThread m_dbThread;
QFileSystemWatcher *m_watcher;
QSet<QString> m_watchedPaths;
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

View File

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

View File

@ -52,12 +52,14 @@ class Download : public QObject
Q_OBJECT
Q_PROPERTY(bool hasNewerRelease READ hasNewerRelease NOTIFY hasNewerReleaseChanged)
Q_PROPERTY(ReleaseInfo releaseInfo READ releaseInfo NOTIFY releaseInfoChanged)
Q_PROPERTY(QString latestNews READ latestNews NOTIFY latestNewsChanged)
public:
static Download *globalInstance();
ReleaseInfo releaseInfo() const;
bool hasNewerRelease() const;
QString latestNews() const { return m_latestNews; }
Q_INVOKABLE void downloadModel(const QString &modelFile);
Q_INVOKABLE void cancelDownload(const QString &modelFile);
Q_INVOKABLE void installModel(const QString &modelFile, const QString &apiKey);
@ -65,11 +67,13 @@ public:
Q_INVOKABLE bool isFirstStart(bool writeVersion = false) const;
public Q_SLOTS:
void updateLatestNews();
void updateReleaseNotes();
private Q_SLOTS:
void handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
void handleReleaseJsonDownloadFinished();
void handleLatestNewsDownloadFinished();
void handleErrorOccurred(QNetworkReply::NetworkError code);
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void handleModelDownloadFinished();
@ -82,6 +86,7 @@ Q_SIGNALS:
void hasNewerReleaseChanged();
void requestHashAndSave(const QString &hash, QCryptographicHash::Algorithm a, const QString &saveFilePath,
QFile *tempFile, QNetworkReply *modelReply);
void latestNewsChanged();
private:
void parseReleaseJsonFile(const QByteArray &jsonData);
@ -92,6 +97,7 @@ private:
HashAndSaveFile *m_hashAndSave;
QMap<QString, ReleaseInfo> m_releaseMap;
QString m_latestNews;
QNetworkAccessManager m_networkManager;
QMap<QNetworkReply*, QFile*> m_activeDownloads;
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 "modellist.h"
#include "mysettings.h"
#include "../gpt4all-backend/llmodel.h"
@ -13,8 +14,8 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QList>
#include <QMutexLocker>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
@ -24,16 +25,20 @@
#include <QtLogging>
#include <exception>
#include <string>
#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()
: QObject(nullptr)
, m_networkManager(new QNetworkAccessManager(this))
, m_model(nullptr)
, m_stopGenerating(false)
{
moveToThread(&m_workerThread);
connect(this, &EmbeddingLLMWorker::requestAtlasQueryEmbedding, this, &EmbeddingLLMWorker::atlasQueryEmbeddingRequested);
connect(this, &EmbeddingLLMWorker::finished, &m_workerThread, &QThread::quit, Qt::DirectConnection);
m_workerThread.setObjectName("embedding");
m_workerThread.start();
@ -58,44 +63,31 @@ void EmbeddingLLMWorker::wait()
bool EmbeddingLLMWorker::loadModel()
{
const EmbeddingModels *embeddingModels = ModelList::globalInstance()->installedEmbeddingModels();
if (!embeddingModels->count())
return false;
m_nomicAPIKey.clear();
m_model = nullptr;
const ModelInfo defaultModel = embeddingModels->defaultModelInfo();
QString filePath = defaultModel.dirpath + defaultModel.filename();
QFileInfo fileInfo(filePath);
if (!fileInfo.exists()) {
qWarning() << "WARNING: Could not load sbert because file does not exist";
m_model = nullptr;
return false;
if (MySettings::globalInstance()->localDocsUseRemoteEmbed()) {
m_nomicAPIKey = MySettings::globalInstance()->localDocsNomicAPIKey();
return true;
}
auto filename = fileInfo.fileName();
bool isNomic = filename.startsWith("gpt4all-nomic-") && filename.endsWith(".rmodel");
if (isNomic) {
QFile file(filePath);
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;
QString filePath = u"%1/../resources/%2"_s.arg(QCoreApplication::applicationDirPath(), LOCAL_EMBEDDING_MODEL);
if (!QFileInfo::exists(filePath)) {
qWarning() << "WARNING: Local embedding model not found";
return false;
}
try {
m_model = LLModel::Implementation::construct(filePath.toStdString());
} catch (const std::exception &e) {
qWarning() << "WARNING: Could not load embedding model:" << e.what();
m_model = nullptr;
return false;
}
// 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
// TODO(cebtenzzre): support GPU-accelerated embeddings
bool success = m_model->loadModel(filePath.toStdString(), 2048, 0);
@ -115,31 +107,38 @@ bool EmbeddingLLMWorker::loadModel()
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
{
return !m_nomicAPIKey.isEmpty();
}
if (!hasModel() && !loadModel()) {
qWarning() << "WARNING: Could not load model for embeddings";
return {};
}
// this function is always called for retrieval tasks
std::vector<float> EmbeddingLLMWorker::generateSyncEmbedding(const QString &text)
{
Q_ASSERT(!isNomic());
std::vector<float> embedding(m_model->embeddingSize());
try {
m_model->embed({text.toStdString()}, embedding.data(), true);
} catch (const std::exception &e) {
qWarning() << "WARNING: LLModel::embed failed: " << e.what();
return {};
if (!isNomic()) {
std::vector<float> embedding(m_model->embeddingSize());
try {
m_model->embed({text.toStdString()}, embedding.data(), true);
} catch (const std::exception &e) {
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;
root.insert("model", "nomic-embed-text-v1");
root.insert("texts", QJsonArray::fromStringList(texts));
@ -148,7 +147,7 @@ void EmbeddingLLMWorker::sendAtlasRequest(const QStringList &texts, const QStrin
QJsonDocument doc(root);
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);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", authorization.toUtf8());
@ -158,50 +157,63 @@ void EmbeddingLLMWorker::sendAtlasRequest(const QStringList &texts, const QStrin
connect(reply, &QNetworkReply::finished, this, &EmbeddingLLMWorker::handleFinished);
}
// this function is always called for retrieval tasks
void EmbeddingLLMWorker::requestSyncEmbedding(const QString &text)
void EmbeddingLLMWorker::atlasQueryEmbeddingRequested(const QString &text)
{
if (!hasModel() && !loadModel()) {
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;
}
if (!isNomic()) {
qWarning() << "WARNING: Request to generate sync embeddings for local model invalid";
return;
}
if (!isNomic()) {
qWarning() << "WARNING: Request to generate sync embeddings for local model invalid";
return;
}
Q_ASSERT(hasModel());
Q_ASSERT(hasModel());
}
sendAtlasRequest({text}, "search_query");
}
// this function is always called for storage into the database
void EmbeddingLLMWorker::requestAsyncEmbedding(const QVector<EmbeddingChunk> &chunks)
void EmbeddingLLMWorker::docEmbeddingsRequested(const QVector<EmbeddingChunk> &chunks)
{
if (m_stopGenerating)
return;
if (!hasModel() && !loadModel()) {
qWarning() << "WARNING: Could not load model for embeddings";
return;
bool isNomic;
{
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;
results.reserve(chunks.size());
for (auto c : chunks) {
for (const auto &c: chunks) {
EmbeddingResult result;
result.model = c.model;
result.folder_id = c.folder_id;
result.chunk_id = c.chunk_id;
// TODO(cebtenzzre): take advantage of batched embeddings
result.embedding.resize(m_model->embeddingSize());
try {
m_model->embed({c.chunk.toStdString()}, result.embedding.data(), false);
} catch (const std::exception &e) {
qWarning() << "WARNING: LLModel::embed failed:" << e.what();
return;
{
QMutexLocker locker(&m_mutex);
try {
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;
}
emit embeddingsGenerated(results);
@ -214,14 +226,15 @@ void EmbeddingLLMWorker::requestAsyncEmbedding(const QVector<EmbeddingChunk> &ch
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;
for (const QJsonValue &innerValue : jsonArray) {
for (const auto &innerValue: jsonArray) {
if (innerValue.isArray()) {
QJsonArray innerArray = innerValue.toArray();
result.reserve(result.size() + innerArray.size());
for (const QJsonValue &value : innerArray) {
for (const auto &value: innerArray) {
result.push_back(static_cast<float>(value.toDouble()));
}
}
@ -230,7 +243,8 @@ std::vector<float> jsonArrayToVector(const QJsonArray &jsonArray) {
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;
if (chunks.size() != embeddings.size()) {
@ -243,10 +257,11 @@ QVector<EmbeddingResult> jsonArrayToEmbeddingResults(const QVector<EmbeddingChun
const QJsonArray embeddingArray = embeddings.at(i).toArray();
std::vector<float> embeddingVector;
for (const QJsonValue& value : embeddingArray)
for (const auto &value: embeddingArray)
embeddingVector.push_back(static_cast<float>(value.toDouble()));
EmbeddingResult result;
result.model = chunk.model;
result.folder_id = chunk.folder_id;
result.chunk_id = chunk.chunk_id;
result.embedding = std::move(embeddingVector);
@ -267,10 +282,6 @@ void EmbeddingLLMWorker::handleFinished()
if (retrievedData.isValid() && retrievedData.canConvert<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);
Q_ASSERT(response.isValid());
bool ok;
@ -279,13 +290,13 @@ void EmbeddingLLMWorker::handleFinished()
QString errorDetails;
QString replyErrorString = reply->errorString().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())
errorDetails += QString(". Error Details: \"%1\"").arg(replyErrorString);
errorDetails += u". Error Details: \"%1\""_s.arg(replyErrorString);
if (!replyContent.isEmpty())
errorDetails += QString(". Response Content: \"%1\"").arg(QString::fromUtf8(replyContent));
errorDetails += u". Response Content: \"%1\""_s.arg(QString::fromUtf8(replyContent));
qWarning() << errorDetails;
emit errorGenerated(folder_id, errorDetails);
emit errorGenerated(chunks, errorDetails);
return;
}
@ -294,7 +305,7 @@ void EmbeddingLLMWorker::handleFinished()
QJsonParseError err;
QJsonDocument document = QJsonDocument::fromJson(jsonData, &err);
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;
}
@ -315,8 +326,8 @@ EmbeddingLLM::EmbeddingLLM()
: QObject(nullptr)
, m_embeddingWorker(new EmbeddingLLMWorker)
{
connect(this, &EmbeddingLLM::requestAsyncEmbedding, m_embeddingWorker,
&EmbeddingLLMWorker::requestAsyncEmbedding, Qt::QueuedConnection);
connect(this, &EmbeddingLLM::requestDocEmbeddings, m_embeddingWorker,
&EmbeddingLLMWorker::docEmbeddingsRequested, Qt::QueuedConnection);
connect(m_embeddingWorker, &EmbeddingLLMWorker::embeddingsGenerated, this,
&EmbeddingLLM::embeddingsGenerated, Qt::QueuedConnection);
connect(m_embeddingWorker, &EmbeddingLLMWorker::errorGenerated, this,
@ -329,26 +340,18 @@ EmbeddingLLM::~EmbeddingLLM()
m_embeddingWorker = nullptr;
}
std::vector<float> EmbeddingLLM::generateEmbeddings(const QString &text)
QString EmbeddingLLM::model()
{
if (!m_embeddingWorker->hasModel() && !m_embeddingWorker->loadModel()) {
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();
return EMBEDDING_MODEL_NAME;
}
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
#include <QByteArray>
#include <QMutex>
#include <QObject>
#include <QString>
#include <QStringList>
@ -16,6 +17,7 @@ class LLModel;
class QNetworkAccessManager;
struct EmbeddingChunk {
QString model; // TODO(jared): use to select model
int folder_id;
int chunk_id;
QString chunk;
@ -24,6 +26,7 @@ struct EmbeddingChunk {
Q_DECLARE_METATYPE(EmbeddingChunk)
struct EmbeddingResult {
QString model;
int folder_id;
int chunk_id;
std::vector<float> embedding;
@ -33,32 +36,33 @@ class EmbeddingLLMWorker : public QObject {
Q_OBJECT
public:
EmbeddingLLMWorker();
virtual ~EmbeddingLLMWorker();
~EmbeddingLLMWorker() override;
void wait();
std::vector<float> lastResponse() const { return m_lastResponse; }
bool loadModel();
bool hasModel() const;
bool isNomic() const;
bool isNomic() const { return !m_nomicAPIKey.isEmpty(); }
bool hasModel() const { return isNomic() || m_model; }
std::vector<float> generateSyncEmbedding(const QString &text);
std::vector<float> generateQueryEmbedding(const QString &text);
public Q_SLOTS:
void requestSyncEmbedding(const QString &text);
void requestAsyncEmbedding(const QVector<EmbeddingChunk> &chunks);
void atlasQueryEmbeddingRequested(const QString &text);
void docEmbeddingsRequested(const QVector<EmbeddingChunk> &chunks);
Q_SIGNALS:
void requestAtlasQueryEmbedding(const QString &text);
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();
private Q_SLOTS:
void handleFinished();
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;
QNetworkAccessManager *m_networkManager;
@ -66,6 +70,7 @@ private:
LLModel *m_model = nullptr;
std::atomic<bool> m_stopGenerating;
QThread m_workerThread;
QMutex m_mutex; // guards m_model and m_nomicAPIKey
};
class EmbeddingLLM : public QObject
@ -73,20 +78,20 @@ class EmbeddingLLM : public QObject
Q_OBJECT
public:
EmbeddingLLM();
virtual ~EmbeddingLLM();
~EmbeddingLLM() override;
static QString model();
bool loadModel();
bool hasModel() const;
public Q_SLOTS:
std::vector<float> generateEmbeddings(const QString &text); // synchronous
void generateAsyncEmbeddings(const QVector<EmbeddingChunk> &chunks);
std::vector<float> generateQueryEmbedding(const QString &text); // synchronous
void generateDocEmbeddingsAsync(const QVector<EmbeddingChunk> &chunks);
Q_SIGNALS:
void requestSyncEmbedding(const QString &text);
void requestAsyncEmbedding(const QVector<EmbeddingChunk> &chunks);
void requestDocEmbeddings(const QVector<EmbeddingChunk> &chunks);
void embeddingsGenerated(const QVector<EmbeddingResult> &embeddings);
void errorGenerated(int folder_id, const QString &error);
void errorGenerated(const QVector<EmbeddingChunk> &chunks, const QString &error);
private:
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>
<!--
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)
-->
<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"/>
</svg>

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>
<!--
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)
-->
<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"/>
</svg>

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>
<!--
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)
-->
<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"/>
</svg>

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

View File

@ -1,6 +1,7 @@
#include "localdocs.h"
#include "database.h"
#include "embllm.h"
#include "mysettings.h"
#include <QCoreApplication>
@ -22,45 +23,37 @@ LocalDocs::LocalDocs()
, m_database(nullptr)
{
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
m_database = new Database(MySettings::globalInstance()->localDocsChunkSize());
m_database = new Database(MySettings::globalInstance()->localDocsChunkSize(),
MySettings::globalInstance()->localDocsFileExtensions());
connect(this, &LocalDocs::requestStart, m_database,
&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,
&Database::addFolder, Qt::QueuedConnection);
connect(this, &LocalDocs::requestRemoveFolder, m_database,
&Database::removeFolder, Qt::QueuedConnection);
connect(this, &LocalDocs::requestChunkSizeChange, m_database,
&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
connect(m_database, &Database::updateInstalled,
m_localDocsModel, &LocalDocsModel::updateInstalled, Qt::QueuedConnection);
connect(m_database, &Database::updateIndexing,
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,
connect(m_database, &Database::requestUpdateGuiForCollectionItem,
m_localDocsModel, &LocalDocsModel::updateCollectionItem, Qt::QueuedConnection);
connect(m_database, &Database::requestAddGuiCollectionItem,
m_localDocsModel, &LocalDocsModel::addCollectionItem, Qt::QueuedConnection);
connect(m_database, &Database::removeFolderById,
connect(m_database, &Database::requestRemoveGuiFolderById,
m_localDocsModel, &LocalDocsModel::removeFolderById, Qt::QueuedConnection);
connect(m_database, &Database::collectionListUpdated,
connect(m_database, &Database::requestGuiCollectionListUpdated,
m_localDocsModel, &LocalDocsModel::collectionListUpdated, Qt::QueuedConnection);
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 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)
{
m_localDocsModel->removeCollectionPath(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()
{
emit requestChunkSizeChange(MySettings::globalInstance()->localDocsChunkSize());
}
void LocalDocs::handleFileExtensionsChanged()
{
emit requestFileExtensionsChange(MySettings::globalInstance()->localDocsFileExtensions());
}

View File

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

View File

@ -3,7 +3,9 @@
#include "localdocs.h"
#include "network.h"
#include <QDateTime>
#include <QMap>
#include <QVector>
#include <QtGlobal>
#include <utility>
@ -12,6 +14,13 @@ LocalDocsCollectionsModel::LocalDocsCollectionsModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
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,
@ -26,11 +35,39 @@ void LocalDocsCollectionsModel::setCollections(const QList<QString> &collections
{
m_collections = collections;
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)
: 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
@ -56,6 +93,8 @@ QVariant LocalDocsModel::data(const QModelIndex &index, int role) const
return item.indexing;
case ErrorRole:
return item.error;
case ForceIndexingRole:
return item.forceIndexing;
case CurrentDocsToIndexRole:
return item.currentDocsToIndex;
case TotalDocsToIndexRole:
@ -68,6 +107,22 @@ QVariant LocalDocsModel::data(const QModelIndex &index, int role) const
return quint64(item.currentEmbeddingsToIndex);
case TotalEmbeddingsToIndexRole:
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();
@ -81,103 +136,94 @@ QHash<int, QByteArray> LocalDocsModel::roleNames() const
roles[InstalledRole] = "installed";
roles[IndexingRole] = "indexing";
roles[ErrorRole] = "error";
roles[ForceIndexingRole] = "forceIndexing";
roles[CurrentDocsToIndexRole] = "currentDocsToIndex";
roles[TotalDocsToIndexRole] = "totalDocsToIndex";
roles[CurrentBytesToIndexRole] = "currentBytesToIndex";
roles[TotalBytesToIndexRole] = "totalBytesToIndex";
roles[CurrentEmbeddingsToIndexRole] = "currentEmbeddingsToIndex";
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;
}
template<typename T>
void LocalDocsModel::updateField(int folder_id, T value,
const std::function<void(CollectionItem&, T)>& updater,
const QVector<int>& roles)
void LocalDocsModel::updateCollectionItem(const CollectionItem &item)
{
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;
updater(m_collectionList[i], value);
emit dataChanged(this->index(i), this->index(i), roles);
QVector<int> changed;
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)
{
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)
void LocalDocsModel::addCollectionItem(const CollectionItem &item)
{
beginInsertRows(QModelIndex(), m_collectionList.size(), m_collectionList.size());
m_collectionList.append(item);
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();) {
if (predicate(m_collectionList.at(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)

View File

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

View File

@ -10,6 +10,8 @@
#include <iostream>
#include <string>
using namespace Qt::Literals::StringLiterals;
class MyLogger: public Logger { };
Q_GLOBAL_STATIC(MyLogger, loggerInstance)
Logger *Logger::globalInstance()
@ -61,7 +63,7 @@ void Logger::messageHandler(QtMsgType type, const QMessageLogContext &, const QS
// Get time and date
auto timestamp = QDateTime::currentDateTime().toString();
// 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.flush();
std::cerr << out;

View File

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

View File

@ -10,14 +10,15 @@ import download
import modellist
import network
import gpt4all
import localdocs
import mysettings
Window {
id: window
width: 1920
height: 1080
minimumWidth: 720
minimumHeight: 480
minimumWidth: 1280
minimumHeight: 720
visible: true
title: qsTr("GPT4All v") + Qt.application.version
@ -32,6 +33,128 @@ Window {
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
PopupDialog {
@ -43,6 +166,18 @@ Window {
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) {
if (window.hasSaved)
return;
@ -61,9 +196,440 @@ Window {
}
}
color: theme.black
color: theme.viewBarBackground
ChatView {
anchors.fill: parent
Rectangle {
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 <QSslConfiguration>
#include <QSslSocket>
#include <QStringList>
#include <QTextStream>
#include <QTimer>
#include <QtLogging>
#include <QUrl>
#include <QtLogging>
#include <algorithm>
#include <compare>
@ -38,12 +39,11 @@
#include <string>
#include <utility>
using namespace Qt::Literals::StringLiterals;
//#define USE_LOCAL_MODELSJSON
const char * const KNOWN_EMBEDDING_MODELS[] {
"all-MiniLM-L6-v2.gguf2.f16.gguf",
"gpt4all-nomic-embed-text-v1.rmodel",
};
static const QStringList FILENAME_BLACKLIST { u"gpt4all-nomic-embed-text-v1.rmodel"_s };
QString ModelInfo::id() const
{
@ -339,56 +339,32 @@ bool ModelInfo::shouldSaveMetadata() const
return installed && (isClone() || isDiscovered() || description() == "" /*indicates sideloaded*/);
}
EmbeddingModels::EmbeddingModels(QObject *parent, bool requireInstalled)
: QSortFilterProxyModel(parent)
QVariantMap ModelInfo::getFields() const
{
m_requireInstalled = requireInstalled;
connect(this, &EmbeddingModels::rowsInserted, this, &EmbeddingModels::countChanged);
connect(this, &EmbeddingModels::rowsRemoved, this, &EmbeddingModels::countChanged);
connect(this, &EmbeddingModels::modelReset, this, &EmbeddingModels::countChanged);
connect(this, &EmbeddingModels::layoutChanged, this, &EmbeddingModels::countChanged);
}
bool EmbeddingModels::filterAcceptsRow(int sourceRow,
const QModelIndex &sourceParent) const
{
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
bool isEmbeddingModel = sourceModel()->data(index, ModelList::IsEmbeddingModelRole).toBool();
bool installed = sourceModel()->data(index, ModelList::InstalledRole).toBool();
QString filename = sourceModel()->data(index, ModelList::FilenameRole).toString();
auto &known = KNOWN_EMBEDDING_MODELS;
if (std::find(known, std::end(known), filename.toStdString()) == std::end(known))
return false; // we are currently not prepared to support other embedding models
return isEmbeddingModel && (!m_requireInstalled || installed);
}
int EmbeddingModels::defaultModelIndex() const
{
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);
return {
{ "filename", m_filename },
{ "description", m_description },
{ "url", m_url },
{ "quant", m_quant },
{ "type", m_type },
{ "isClone", m_isClone },
{ "isDiscovered", m_isDiscovered },
{ "likes", m_likes },
{ "downloads", m_downloads },
{ "recency", m_recency },
{ "temperature", m_temperature },
{ "topP", m_topP },
{ "minP", m_minP },
{ "topK", m_topK },
{ "maxLength", m_maxLength },
{ "promptBatchSize", m_promptBatchSize },
{ "contextLength", m_contextLength },
{ "gpuLayers", m_gpuLayers },
{ "repeatPenalty", m_repeatPenalty },
{ "repeatPenaltyTokens", m_repeatPenaltyTokens },
{ "promptTemplate", m_promptTemplate },
{ "systemPrompt", m_systemPrompt },
};
}
InstalledModels::InstalledModels(QObject *parent)
@ -424,13 +400,14 @@ DownloadableModels::DownloadableModels(QObject *parent)
bool DownloadableModels::filterAcceptsRow(int sourceRow,
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);
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
bool isDownloadable = !sourceModel()->data(index, ModelList::DescriptionRole).toString().isEmpty();
bool isInstalled = !sourceModel()->data(index, ModelList::InstalledRole).toString().isEmpty();
bool isIncomplete = !sourceModel()->data(index, ModelList::IncompleteRole).toString().isEmpty();
bool isInstalled = sourceModel()->data(index, ModelList::InstalledRole).toBool();
bool isIncomplete = sourceModel()->data(index, ModelList::IncompleteRole).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
@ -468,9 +445,7 @@ ModelList *ModelList::globalInstance()
ModelList::ModelList()
: QAbstractListModel(nullptr)
, m_embeddingModels(new EmbeddingModels(this, false /* all models */))
, m_installedModels(new InstalledModels(this))
, m_installedEmbeddingModels(new EmbeddingModels(this, true /* installed models */))
, m_downloadableModels(new DownloadableModels(this))
, m_asyncModelRequestOngoing(false)
, m_discoverLimit(20)
@ -480,9 +455,7 @@ ModelList::ModelList()
, m_discoverResultsCompleted(0)
, m_discoverInProgress(false)
{
m_embeddingModels->setSourceModel(this);
m_installedModels->setSourceModel(this);
m_installedEmbeddingModels->setSourceModel(this);
m_downloadableModels->setSourceModel(this);
connect(MySettings::globalInstance(), &MySettings::modelPathChanged, this, &ModelList::updateModelsFromDirectory);
@ -552,17 +525,11 @@ const QList<QString> ModelList::userDefaultModelList() const
return models;
}
int ModelList::defaultEmbeddingModelIndex() const
{
return embeddingModels()->defaultModelIndex();
}
ModelInfo ModelList::defaultModelInfo() const
{
QMutexLocker locker(&m_mutex);
QSettings settings;
settings.sync();
// 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.
@ -1153,7 +1120,7 @@ void ModelList::removeInternal(const ModelInfo &model)
QString ModelList::uniqueModelName(const ModelInfo &model) const
{
QMutexLocker locker(&m_mutex);
QRegularExpression re("^(.*)~(\\d+)$");
static const QRegularExpression re("^(.*)~(\\d+)$");
QRegularExpressionMatch match = re.match(model.name());
QString baseName;
if (match.hasMatch())
@ -1208,13 +1175,11 @@ void ModelList::updateModelsFromDirectory()
it.next();
if (!it.fileInfo().isDir()) {
QString filename = it.fileName();
if (filename.endsWith(".txt") && (filename.startsWith("chatgpt-") || filename.startsWith("nomic-"))) {
if (filename.startsWith("chatgpt-") && filename.endsWith(".txt")) {
QString apikey;
QString modelname(filename);
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);
if (file.open(QIODevice::ReadWrite)) {
QTextStream in(&file);
@ -1227,7 +1192,7 @@ void ModelList::updateModelsFromDirectory()
obj.insert("modelName", modelname);
QJsonDocument doc(obj);
auto newfilename = QString("gpt4all-%1.rmodel").arg(modelname);
auto newfilename = u"gpt4all-%1.rmodel"_s.arg(modelname);
QFile newfile(path + newfilename);
if (newfile.open(QIODevice::ReadWrite)) {
QTextStream out(&newfile);
@ -1241,46 +1206,41 @@ void ModelList::updateModelsFromDirectory()
};
auto processDirectory = [&](const QString& path) {
QDirIterator it(path, QDirIterator::Subdirectories);
QDirIterator it(path, QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
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();
QFileInfo info(filePath);
if (modelsById.isEmpty()) {
if (!contains(filename))
addModel(filename);
modelsById.append(filename);
}
if (!info.exists())
continue;
QFileInfo info = it.fileInfo();
QVector<QString> modelsById;
{
QMutexLocker locker(&m_mutex);
for (ModelInfo *info : m_models)
if (info->filename() == filename)
modelsById.append(info->id());
}
if (modelsById.isEmpty()) {
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);
}
}
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()
{
#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
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
QNetworkRequest request(jsonUrl);
QSslConfiguration conf = request.sslConfiguration();
@ -1343,9 +1303,9 @@ void ModelList::updateModelsFromJsonAsync()
emit asyncModelRequestOngoingChanged();
#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
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
QNetworkRequest request(jsonUrl);
QSslConfiguration conf = request.sslConfiguration();
@ -1383,7 +1343,7 @@ void ModelList::handleModelsJsonDownloadErrorOccurred(QNetworkReply::NetworkErro
if (!reply)
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());
}
@ -1399,7 +1359,8 @@ void ModelList::updateDataForSettings()
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 bParts = b.split('.');
@ -1450,8 +1411,8 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
QString versionRemoved = obj["removedIn"].toString();
QString url = obj["url"].toString();
QByteArray modelHash = obj["md5sum"].toString().toLatin1();
bool isDefault = obj.contains("isDefault") && obj["isDefault"] == QString("true");
bool disableGUI = obj.contains("disableGUI") && obj["disableGUI"] == QString("true");
bool isDefault = obj.contains("isDefault") && obj["isDefault"] == u"true"_s;
bool disableGUI = obj.contains("disableGUI") && obj["disableGUI"] == u"true"_s;
QString description = obj["description"].toString();
QString order = obj["order"].toString();
int ramrequired = obj["ramrequired"].toString().toInt();
@ -1587,7 +1548,7 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
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"
" 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>");
@ -1668,38 +1629,6 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
};
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)
@ -1723,9 +1652,8 @@ void ModelList::updateDiscoveredInstalled(const ModelInfo &info)
void ModelList::updateModelsFromSettings()
{
QSettings settings;
settings.sync();
QStringList groups = settings.childGroups();
for (const QString g : groups) {
for (const QString &g: groups) {
if (!g.startsWith("model-"))
continue;
@ -1913,7 +1841,7 @@ void ModelList::discoverSearch(const QString &search)
m_discoverNumberOfResults = 0;
m_discoverResultsCompleted = 0;
discoverProgressChanged();
emit discoverProgressChanged();
if (search.isEmpty()) {
return;
@ -1922,9 +1850,10 @@ void ModelList::discoverSearch(const QString &search)
m_discoverInProgress = true;
emit discoverInProgressChanged();
QStringList searchParams = search.split(QRegularExpression("\\s+")); // split by whitespace
QString searchString = QString("search=%1&").arg(searchParams.join('+'));
QString limitString = m_discoverLimit > 0 ? QString("limit=%1&").arg(m_discoverLimit) : QString();
static const QRegularExpression wsRegex("\\s+");
QStringList searchParams = search.split(wsRegex); // split by whitespace
QString searchString = u"search=%1&"_s.arg(searchParams.join('+'));
QString limitString = m_discoverLimit > 0 ? u"limit=%1&"_s.arg(m_discoverLimit) : QString();
QString sortString;
switch (m_discoverSort) {
@ -1937,9 +1866,10 @@ void ModelList::discoverSearch(const QString &search)
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);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
@ -1965,7 +1895,7 @@ void ModelList::handleDiscoveryErrorOccurred(QNetworkReply::NetworkError code)
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
if (!reply)
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();
}
@ -2005,7 +1935,7 @@ void ModelList::parseDiscoveryJsonFile(const QByteArray &jsonData)
qWarning() << "ERROR: Couldn't parse: " << jsonData << err.errorString();
m_discoverNumberOfResults = 0;
m_discoverResultsCompleted = 0;
discoverProgressChanged();
emit discoverProgressChanged();
m_discoverInProgress = false;
emit discoverInProgressChanged();
return;
@ -2045,7 +1975,7 @@ void ModelList::parseDiscoveryJsonFile(const QByteArray &jsonData)
QString filename = file.second;
++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);
request.setRawHeader("Accept-Encoding", "identity");
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy);
@ -2084,21 +2014,16 @@ void ModelList::handleDiscoveryItemFinished()
QJsonObject config = obj["config"].toObject();
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 linkedEtagHeader = reply->rawHeader("X-Linked-Etag");
// For some reason these seem to contain quotation marks ewww
linkedEtagHeader.replace("\"", "");
linkedEtagHeader.replace("\'", "");
QString locationHeader = reply->header(QNetworkRequest::LocationHeader).toString();
QString repoCommit = QString::fromUtf8(repoCommitHeader);
QString linkedSize = QString::fromUtf8(linkedSizeHeader);
QString linkedEtag = QString::fromUtf8(linkedEtagHeader);
// QString locationHeader = reply->header(QNetworkRequest::LocationHeader).toString();
QString modelFilename = reply->request().attribute(QNetworkRequest::UserMax).toString();
QString modelFilesize = linkedSize;
modelFilesize = ModelList::toFileSize(modelFilesize.toULongLong());
QString modelFilesize = ModelList::toFileSize(QString(linkedSizeHeader).toULongLong());
QString description = tr("<strong>Created by %1.</strong><br><ul>"
"<li>Published on %2."
@ -2155,6 +2080,6 @@ void ModelList::handleDiscoveryItemErrorOccurred(QNetworkReply::NetworkError cod
if (!reply)
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();
}

View File

@ -20,6 +20,8 @@
#include <QtGlobal>
#include <QtQml>
using namespace Qt::Literals::StringLiterals;
struct ModelInfo {
Q_GADGET
Q_PROPERTY(QString id READ id WRITE setId)
@ -169,6 +171,8 @@ public:
bool shouldSaveMetadata() const;
private:
QVariantMap getFields() const;
QString m_id;
QString m_name;
QString m_filename;
@ -199,28 +203,6 @@ private:
};
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
{
Q_OBJECT
@ -269,8 +251,6 @@ class ModelList : public QAbstractListModel
{
Q_OBJECT
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(DownloadableModels* downloadableModels READ downloadableModels NOTIFY downloadableModelsChanged)
Q_PROPERTY(QList<QString> userDefaultModelList READ userDefaultModelList NOTIFY userDefaultModelListChanged)
@ -408,7 +388,6 @@ public:
Q_INVOKABLE void removeClone(const ModelInfo &model);
Q_INVOKABLE void removeInstalled(const ModelInfo &model);
ModelInfo defaultModelInfo() const;
int defaultEmbeddingModelIndex() const;
void addModel(const QString &id);
void changeId(const QString &oldId, const QString &newId);
@ -416,20 +395,18 @@ public:
const QList<ModelInfo> exportModelList() const;
const QList<QString> userDefaultModelList() const;
EmbeddingModels *embeddingModels() const { return m_embeddingModels; }
EmbeddingModels *installedEmbeddingModels() const { return m_installedEmbeddingModels; }
InstalledModels *installedModels() const { return m_installedModels; }
DownloadableModels *downloadableModels() const { return m_downloadableModels; }
static inline QString toFileSize(quint64 sz) {
if (sz < 1024) {
return QString("%1 bytes").arg(sz);
return u"%1 bytes"_s.arg(sz);
} 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) {
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 {
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:
void countChanged();
void installedEmbeddingModelsChanged();
void installedModelsChanged();
void downloadableModelsChanged();
void userDefaultModelListChanged();
@ -494,8 +470,6 @@ private:
private:
mutable QMutex m_mutex;
QNetworkAccessManager m_networkManager;
EmbeddingModels *m_embeddingModels;
EmbeddingModels *m_installedEmbeddingModels;
InstalledModels *m_installedModels;
DownloadableModels *m_downloadableModels;
QList<ModelInfo*> m_models;

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -36,6 +36,8 @@
#include <cstring>
#include <utility>
using namespace Qt::Literals::StringLiterals;
//#define DEBUG
static const char MIXPANEL_TOKEN[] = "ce362e568ddaee16ed243eaffb5860a2";
@ -43,7 +45,8 @@ static const char MIXPANEL_TOKEN[] = "ce362e568ddaee16ed243eaffb5860a2";
#if defined(Q_OS_MAC)
#include <sys/sysctl.h>
static QString getCPUModel() {
static QString getCPUModel()
{
char buffer[256];
size_t bufferlen = sizeof(buffer);
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)
#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");
}
#else
#define get_cpuid(level, regs) __cpuid(regs, level)
#endif
static QString getCPUModel() {
static QString getCPUModel()
{
int regs[12];
// EAX=800000000h: Get Highest Extended Function Implemented
@ -98,10 +103,8 @@ Network::Network()
: QObject{nullptr}
{
QSettings settings;
settings.sync();
m_uniqueId = settings.value("uniqueId", generateUniqueId()).toString();
settings.setValue("uniqueId", m_uniqueId);
settings.sync();
m_sessionId = generateUniqueId();
// allow sendMixpanel to be called from any thread
@ -275,7 +278,7 @@ void Network::sendStartup()
const auto *display = QGuiApplication::primaryScreen();
trackEvent("startup", {
{"$screen_dpi", std::round(display->physicalDotsPerInch())},
{"display", QString("%1x%2").arg(display->size().width()).arg(display->size().height())},
{"display", u"%1x%2"_s.arg(display->size().width()).arg(display->size().height())},
{"ram", LLM::globalInstance()->systemTotalRAMInGB()},
{"cpu", getCPUModel()},
{"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 mysettings
import network
import llm
MySettingsTab {
onRestoreDefaultsClicked: {
MySettings.restoreApplicationDefaults();
}
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 {
id: applicationSettingsTabInner
columns: 3
rowSpacing: 10
rowSpacing: 30
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 {
id: themeLabel
text: qsTr("Theme")
helpText: qsTr("Customize the colors of GPT4All")
Layout.row: 1
Layout.column: 0
}
MyComboBox {
id: themeBox
Layout.row: 1
Layout.column: 1
Layout.columnSpan: 1
Layout.column: 2
Layout.minimumWidth: 200
Layout.maximumWidth: 200
Layout.fillWidth: false
model: ["Dark", "Light", "LegacyDark"]
Layout.alignment: Qt.AlignRight
model: [qsTr("Dark"), qsTr("Light"), qsTr("LegacyDark")]
Accessible.role: Accessible.ComboBox
Accessible.name: qsTr("Color theme")
Accessible.description: qsTr("Color theme for the chat client to use")
@ -54,16 +120,18 @@ MySettingsTab {
MySettingsLabel {
id: fontLabel
text: qsTr("Font Size")
helpText: qsTr("How big your font is displayed")
Layout.row: 2
Layout.column: 0
}
MyComboBox {
id: fontBox
Layout.row: 2
Layout.column: 1
Layout.columnSpan: 1
Layout.minimumWidth: 100
Layout.column: 2
Layout.minimumWidth: 200
Layout.maximumWidth: 200
Layout.fillWidth: false
Layout.alignment: Qt.AlignRight
model: ["Small", "Medium", "Large"]
Accessible.role: Accessible.ComboBox
Accessible.name: qsTr("Font size")
@ -87,16 +155,18 @@ MySettingsTab {
MySettingsLabel {
id: deviceLabel
text: qsTr("Device")
helpText: qsTr("The hardware device used to load the model")
Layout.row: 3
Layout.column: 0
}
MyComboBox {
id: deviceBox
Layout.row: 3
Layout.column: 1
Layout.columnSpan: 1
Layout.minimumWidth: 350
Layout.column: 2
Layout.minimumWidth: 400
Layout.maximumWidth: 400
Layout.fillWidth: false
Layout.alignment: Qt.AlignRight
model: MySettings.deviceList
Accessible.role: Accessible.ComboBox
Accessible.name: qsTr("Device")
@ -123,16 +193,17 @@ MySettingsTab {
MySettingsLabel {
id: defaultModelLabel
text: qsTr("Default model")
helpText: qsTr("The preferred default model")
Layout.row: 4
Layout.column: 0
}
MyComboBox {
id: comboBox
Layout.row: 4
Layout.column: 1
Layout.columnSpan: 2
Layout.minimumWidth: 350
Layout.fillWidth: true
Layout.column: 2
Layout.minimumWidth: 400
Layout.maximumWidth: 400
Layout.alignment: Qt.AlignRight
model: ModelList.userDefaultModelList
Accessible.role: Accessible.ComboBox
Accessible.name: qsTr("Default model")
@ -156,45 +227,96 @@ MySettingsTab {
MySettingsLabel {
id: modelPathLabel
text: qsTr("Download path")
helpText: qsTr("The download folder for models")
Layout.row: 5
Layout.column: 0
}
MyDirectoryField {
id: modelPathDisplayField
text: MySettings.modelPath
font.pixelSize: theme.fontSizeLarge
implicitWidth: 300
RowLayout {
Layout.row: 5
Layout.column: 1
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
Layout.column: 2
Layout.alignment: Qt.AlignRight
Layout.minimumWidth: 400
Layout.maximumWidth: 400
spacing: 10
MyDirectoryField {
id: modelPathDisplayField
text: MySettings.modelPath
font.pixelSize: theme.fontSizeLarge
implicitWidth: 300
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: {
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
text: qsTr("Browse")
Accessible.description: qsTr("Choose where to save model files")
Layout.alignment: Qt.AlignRight
checked: MySettings.networkIsActive
onClicked: {
openFolderDialog("file://" + MySettings.modelPath, function(selectedFolder) {
MySettings.modelPath = selectedFolder
})
if (MySettings.networkIsActive) {
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 {
id: nThreadsLabel
text: qsTr("CPU Threads")
Layout.row: 6
helpText: qsTr("Number of CPU threads for inference and embedding")
Layout.row: 8
Layout.column: 0
}
MyTextField {
@ -203,8 +325,11 @@ MySettingsTab {
font.pixelSize: theme.fontSizeLarge
ToolTip.text: qsTr("Amount of processing threads to use bounded by 1 and number of logical processors")
ToolTip.visible: hovered
Layout.row: 6
Layout.column: 1
Layout.alignment: Qt.AlignRight
Layout.row: 8
Layout.column: 2
Layout.minimumWidth: 200
Layout.maximumWidth: 200
validator: IntValidator {
bottom: 1
}
@ -223,14 +348,16 @@ MySettingsTab {
}
MySettingsLabel {
id: saveChatsContextLabel
text: qsTr("Save chats context to disk")
Layout.row: 7
text: qsTr("Save chat context")
helpText: qsTr("Save chat context to disk")
Layout.row: 9
Layout.column: 0
}
MyCheckBox {
id: saveChatsContextBox
Layout.row: 7
Layout.column: 1
Layout.row: 9
Layout.column: 2
Layout.alignment: Qt.AlignRight
checked: MySettings.saveChatsContext
onClicked: {
MySettings.saveChatsContext = !MySettings.saveChatsContext
@ -241,13 +368,15 @@ MySettingsTab {
MySettingsLabel {
id: serverChatLabel
text: qsTr("Enable API server")
Layout.row: 8
helpText: qsTr("A local http server running on local port")
Layout.row: 10
Layout.column: 0
}
MyCheckBox {
id: serverChatBox
Layout.row: 8
Layout.column: 1
Layout.row: 10
Layout.column: 2
Layout.alignment: Qt.AlignRight
checked: MySettings.serverChat
onClicked: {
MySettings.serverChat = !MySettings.serverChat
@ -257,8 +386,9 @@ MySettingsTab {
}
MySettingsLabel {
id: serverPortLabel
text: qsTr("API Server Port (Requires restart):")
Layout.row: 9
text: qsTr("API Server Port:")
helpText: qsTr("A local port to run the server (Requires restart")
Layout.row: 11
Layout.column: 0
}
MyTextField {
@ -268,8 +398,11 @@ MySettingsTab {
font.pixelSize: theme.fontSizeLarge
ToolTip.text: qsTr("Api server port. WARNING: You need to restart the application for it to take effect")
ToolTip.visible: hovered
Layout.row: 9
Layout.column: 1
Layout.row: 11
Layout.column: 2
Layout.minimumWidth: 200
Layout.maximumWidth: 200
Layout.alignment: Qt.AlignRight
validator: IntValidator {
bottom: 1
}
@ -286,58 +419,53 @@ MySettingsTab {
Accessible.name: serverPortField.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 {
id: gpuOverrideLabel
text: qsTr("Force Metal (macOS+arm)")
Layout.row: 1
Layout.row: 13
Layout.column: 0
}
RowLayout {
Layout.row: 1
Layout.column: 1
Layout.columnSpan: 2
MyCheckBox {
id: gpuOverrideBox
checked: MySettings.forceMetal
onClicked: {
MySettings.forceMetal = !MySettings.forceMetal
}
MyCheckBox {
id: gpuOverrideBox
Layout.row: 13
Layout.column: 2
Layout.alignment: Qt.AlignRight
checked: MySettings.forceMetal
onClicked: {
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 {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
Layout.minimumHeight: warningLabel.height
MySettingsLabel {
id: warningLabel
width: parent.width
color: theme.textErrorColor
wrapMode: Text.WordWrap
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.")
}
MySettingsLabel {
id: updatesLabel
text: qsTr("Check for updates")
helpText: qsTr("Click to see if an update to the application is available");
Layout.row: 14
Layout.column: 0
}
MySettingsButton {
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
}
signal downloadClicked
signal aboutClicked
color: theme.viewBackground
color: theme.containerBackground
Rectangle {
id: borderRight
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
width: 2
color: theme.dividerColor
}
Item {
anchors.fill: parent
anchors.margins: 10
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: borderRight.left
Accessible.role: Accessible.Pane
Accessible.name: qsTr("Drawer")
Accessible.description: qsTr("Main navigation drawer")
MyButton {
MySettingsButton {
id: newChat
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 20
font.pixelSize: theme.fontSizeLarger
topPadding: 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 {
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: -10
anchors.topMargin: 10
anchors.top: newChat.bottom
anchors.bottom: checkForUpdatesButton.top
anchors.bottomMargin: 10
anchors.topMargin: 15
anchors.top: divider.bottom
anchors.bottom: parent.bottom
anchors.bottomMargin: 15
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
clip: true
ListView {
id: conversationList
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
model: ChatListModel
@ -71,6 +92,33 @@ Rectangle {
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 {
id: chatRectangle
width: conversationList.width
@ -80,21 +128,25 @@ Rectangle {
property bool trashQuestionDisplayed: false
visible: !isServer || MySettings.serverChat
z: isCurrent ? 199 : 1
color: index % 2 === 0 ? theme.darkContrast : theme.lightContrast
color: isCurrent ? theme.selectedBackground : "transparent"
border.width: isCurrent
border.color: chatName.readOnly ? theme.assistantColor : theme.userColor
border.color: theme.dividerColor
radius: 10
TextField {
id: chatName
anchors.left: parent.left
anchors.right: buttons.left
color: theme.textColor
padding: 15
color: theme.styledTextColor
topPadding: 15
bottomPadding: 15
focus: false
readOnly: true
wrapMode: Text.NoWrap
hoverEnabled: false // Disable hover events on the TextArea
selectByMouse: false // Disable text selection in the TextArea
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: readOnly ? metrics.elidedText : name
horizontalAlignment: TextInput.AlignLeft
opacity: trashQuestionDisplayed ? 0.5 : 1.0
@ -103,7 +155,7 @@ Rectangle {
font: chatName.font
text: name
elide: Text.ElideRight
elideWidth: chatName.width - 40
elideWidth: chatName.width - 15
}
background: Rectangle {
color: "transparent"
@ -240,45 +292,5 @@ Rectangle {
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();
}
property bool hasEmbeddingModel: ModelList.installedEmbeddingModels.count !== 0
showAdvancedSettingsButton: hasEmbeddingModel
showRestoreDefaultsButton: hasEmbeddingModel
showRestoreDefaultsButton: true
title: qsTr("LocalDocs")
contentItem: ColumnLayout {
id: root
spacing: 10
property alias collection: collection.text
property alias folder_path: folderEdit.text
MySettingsLabel {
id: downloadLabel
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.")
Label {
color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: "Indexing"
}
MySettingsButton {
visible: !hasEmbeddingModel
Layout.topMargin: 20
Layout.alignment: Qt.AlignLeft
text: qsTr("Download")
font.pixelSize: theme.fontSizeLarger
onClicked: {
downloadClicked()
Rectangle {
Layout.bottomMargin: 15
Layout.fillWidth: true
height: 2
color: theme.settingsDivider
}
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 {
visible: hasEmbeddingModel
Layout.fillWidth: true
height: row.height
RowLayout {
id: row
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()
}
}
}
Label {
Layout.topMargin: 15
color: theme.grayRed900
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: "Embedding"
}
ColumnLayout {
visible: hasEmbeddingModel
spacing: 0
Repeater {
model: LocalDocs.localDocsModel
delegate: Rectangle {
id: item
Layout.fillWidth: true
height: buttons.height + 20
color: index % 2 === 0 ? theme.darkContrast : theme.lightContrast
property bool removing: false
Rectangle {
Layout.bottomMargin: 15
Layout.fillWidth: true
height: 2
color: theme.grayRed500
}
Text {
id: collectionId
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.margins: 20
text: collection
elide: Text.ElideRight
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
width: 200
}
RowLayout {
MySettingsLabel {
text: qsTr("Use Nomic Embed API")
helpText: qsTr("Embed documents using the fast Nomic API instead of a private local model.")
}
Text {
id: folderId
anchors.left: collectionId.right
anchors.right: buttons.left
anchors.margins: 20
anchors.verticalCenter: parent.verticalCenter
text: folder_path
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)
}
}
}
MyCheckBox {
id: useNomicAPIBox
Component.onCompleted: {
useNomicAPIBox.checked = MySettings.localDocsUseRemoteEmbed;
}
onClicked: {
MySettings.localDocsUseRemoteEmbed = useNomicAPIBox.checked && MySettings.localDocsNomicAPIKey !== "";
}
}
}
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 {
id: showReferencesLabel
text: qsTr("Show references")
text: qsTr("Show sources")
helpText: qsTr("Shows sources in GUI generated by localdocs")
}
MyCheckBox {
id: showReferencesBox
@ -202,104 +170,92 @@ MySettingsTab {
onClicked: {
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 {
visible: hasEmbeddingModel
Layout.bottomMargin: 15
Layout.fillWidth: true
height: 3
color: theme.accentColor
}
}
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
height: 2
color: theme.settingsDivider
}
MySettingsLabel {
id: chunkLabel
Layout.row: 1
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
id: warningLabel
Layout.bottomMargin: 15
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
Layout.minimumHeight: warningLabel.height
color: theme.textErrorColor
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 {
id: warningLabel
width: parent.width
color: theme.textErrorColor
wrapMode: Text.WordWrap
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) }
id: chunkLabel
Layout.fillWidth: true
text: qsTr("Document snippet size (characters)")
helpText: qsTr("Number of characters per document snippet. Larger numbers increase likelihood of factual responses, but also result in slower generation.")
}
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
rowSpacing: 10
columnSpacing: 10
enabled: ModelList.installedModels.count !== 0
property var currentModelName: comboBox.currentText
property var currentModelId: comboBox.currentValue
property var currentModelInfo: ModelList.modelInfo(root.currentModelId)
MySettingsLabel {
id: label
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 {
id: label
Layout.row: 1
Layout.column: 0
text: qsTr("Model/Character")
helpText: qsTr("Select or clone a model and change its settings")
}
RowLayout {
Layout.fillWidth: true
Layout.row: 1
Layout.row: 2
Layout.column: 0
Layout.columnSpan: 2
height: label.height + 20
@ -95,20 +117,14 @@ MySettingsTab {
}
RowLayout {
Layout.row: 2
Layout.row: 3
Layout.column: 0
Layout.topMargin: 15
spacing: 10
MySettingsLabel {
id: uniqueNameLabel
text: qsTr("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
helpText: qsTr("Must contain a non-empty unique name")
}
}
@ -117,7 +133,7 @@ MySettingsTab {
text: root.currentModelName
font.pixelSize: theme.fontSizeLarge
enabled: root.currentModelInfo.isClone || root.currentModelInfo.description === ""
Layout.row: 3
Layout.row: 4
Layout.column: 0
Layout.columnSpan: 2
Layout.fillWidth: true
@ -137,14 +153,13 @@ MySettingsTab {
if (text !== "" && ModelList.isUniqueName(text)) {
MySettings.setModelName(root.currentModelInfo, text);
}
uniqueNameLabelHelp.visible = root.currentModelInfo.name !== "" &&
(text === "" || (text !== root.currentModelInfo.name && !ModelList.isUniqueName(text)));
}
}
MySettingsLabel {
text: qsTr("Model File")
Layout.row: 4
helpText: qsTr("The filename of the selected model")
Layout.row: 5
Layout.column: 0
Layout.topMargin: 15
}
@ -153,7 +168,7 @@ MySettingsTab {
text: root.currentModelInfo.filename
font.pixelSize: theme.fontSizeLarge
enabled: false
Layout.row: 5
Layout.row: 6
Layout.column: 0
Layout.columnSpan: 2
Layout.fillWidth: true
@ -162,7 +177,8 @@ MySettingsTab {
MySettingsLabel {
visible: !root.currentModelInfo.isOnline
text: qsTr("System Prompt")
Layout.row: 6
helpText: qsTr("Prefixed at the beginning of every conversation")
Layout.row: 7
Layout.column: 0
Layout.topMargin: 15
}
@ -170,7 +186,7 @@ MySettingsTab {
Rectangle {
id: systemPrompt
visible: !root.currentModelInfo.isOnline
Layout.row: 7
Layout.row: 8
Layout.column: 0
Layout.columnSpan: 2
Layout.fillWidth: true
@ -203,7 +219,7 @@ MySettingsTab {
}
RowLayout {
Layout.row: 8
Layout.row: 9
Layout.column: 0
Layout.columnSpan: 2
Layout.topMargin: 15
@ -211,6 +227,7 @@ MySettingsTab {
MySettingsLabel {
id: promptTemplateLabel
text: qsTr("Prompt Template")
helpText: qsTr("The template that wraps every prompt")
}
MySettingsLabel {
id: promptTemplateLabelHelp
@ -223,7 +240,7 @@ MySettingsTab {
Rectangle {
id: promptTemplate
Layout.row: 9
Layout.row: 10
Layout.column: 0
Layout.columnSpan: 2
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 {
Layout.row: 11
Layout.column: 0
Layout.columnSpan: 2
Layout.topMargin: 15
Layout.fillWidth: true
Layout.minimumWidth: promptTemplate.width
columns: 4
rowSpacing: 10
rowSpacing: 30
columnSpacing: 10
MySettingsLabel {
id: contextLengthLabel
visible: !root.currentModelInfo.isOnline
text: qsTr("Context Length")
helpText: qsTr("Conversation context window")
Layout.row: 0
Layout.column: 0
}
@ -374,6 +378,7 @@ MySettingsTab {
MySettingsLabel {
id: tempLabel
text: qsTr("Temperature")
helpText: qsTr("The temperature for model token generation")
Layout.row: 1
Layout.column: 2
}
@ -418,6 +423,7 @@ MySettingsTab {
MySettingsLabel {
id: topPLabel
text: qsTr("Top P")
helpText: qsTr("Prevents choosing highly unlikely tokens")
Layout.row: 2
Layout.column: 0
}
@ -461,6 +467,7 @@ MySettingsTab {
MySettingsLabel {
id: minPLabel
text: qsTr("Min P")
helpText: qsTr("Minimum relative probability")
Layout.row: 3
Layout.column: 0
}
@ -506,6 +513,7 @@ MySettingsTab {
id: topKLabel
visible: !root.currentModelInfo.isOnline
text: qsTr("Top K")
helpText: qsTr("Size of selection pool for tokens")
Layout.row: 2
Layout.column: 2
}
@ -551,6 +559,7 @@ MySettingsTab {
id: maxLengthLabel
visible: !root.currentModelInfo.isOnline
text: qsTr("Max Length")
helpText: qsTr("Maximum length of response in tokens")
Layout.row: 0
Layout.column: 2
}
@ -597,6 +606,7 @@ MySettingsTab {
id: batchSizeLabel
visible: !root.currentModelInfo.isOnline
text: qsTr("Prompt Batch Size")
helpText: qsTr("Amount of prompt tokens to process at once")
Layout.row: 1
Layout.column: 0
}
@ -642,6 +652,7 @@ MySettingsTab {
id: repeatPenaltyLabel
visible: !root.currentModelInfo.isOnline
text: qsTr("Repeat Penalty")
helpText: qsTr("Penalize repetitiveness")
Layout.row: 4
Layout.column: 2
}
@ -687,6 +698,7 @@ MySettingsTab {
id: repeatPenaltyTokensLabel
visible: !root.currentModelInfo.isOnline
text: qsTr("Repeat Penalty Tokens")
helpText: qsTr("Length to apply penalty")
Layout.row: 3
Layout.column: 2
}
@ -733,6 +745,7 @@ MySettingsTab {
id: gpuLayersLabel
visible: !root.currentModelInfo.isOnline
text: qsTr("GPU Layers")
helpText: qsTr("How many GPU layers to load into VRAM")
Layout.row: 4
Layout.column: 0
}
@ -790,9 +803,8 @@ MySettingsTab {
Layout.columnSpan: 2
Layout.topMargin: 15
Layout.fillWidth: true
Layout.minimumWidth: promptTemplate.width
height: 3
color: theme.accentColor
height: 2
color: theme.settingsDivider
}
}
}

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

View File

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

View File

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

View File

@ -12,6 +12,8 @@ TextField {
background: Rectangle {
implicitWidth: 150
color: theme.controlBackground
border.width: 1
border.color: theme.controlBorder
radius: 10
}
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 {
id: image
anchors.centerIn: parent
visible: false
mipmap: true
width: 20
height: 20
sourceSize.width: 20
sourceSize.height: 20
}
ColorOverlay {
anchors.fill: image

View File

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

View File

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

View File

@ -2,9 +2,40 @@ import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
Label {
color: theme.settingsTitleTextColor
font.pixelSize: theme.fontSizeSmall
font.bold: true
ColumnLayout {
id: root
property alias text: mainTextLabel.text
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
}
property alias title: titleLabelText.text
property ListModel tabTitlesModel: ListModel { }
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 {
id: settingsTabBar
anchors.top: titleLabel.bottom
anchors.topMargin: 15
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width / 1.75
z: 200
@ -89,8 +57,8 @@ Item {
anchors.rightMargin: 15
anchors.left: parent.left
anchors.right: parent.right
height: 3
color: theme.accentColor
height: 2
color: theme.settingsDivider
}
FolderDialog {
@ -106,7 +74,8 @@ Item {
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.right: parent.right
anchors.bottom: parent.bottom

View File

@ -8,12 +8,9 @@ Item {
id: root
property string title: ""
property Item contentItem: null
property Item advancedSettings: null
property bool showAdvancedSettingsButton: true
property bool showRestoreDefaultsButton: true
property var openFolderDialog
signal restoreDefaultsClicked
signal downloadClicked
onContentItemChanged: function() {
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 {
id: scrollView
width: parent.width
@ -61,14 +50,9 @@ Item {
Layout.fillWidth: true
}
Column {
id: advancedInner
visible: false
Layout.fillWidth: true
}
Item {
Layout.fillWidth: true
Layout.topMargin: 20
height: restoreDefaultsButton.height
MySettingsButton {
id: restoreDefaultsButton
@ -84,20 +68,6 @@ Item {
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