Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c091491b88 | ||
|
|
5add87e85b | ||
|
|
33f805d60a | ||
|
|
cd83f7c2f0 | ||
|
|
75729951a9 |
@@ -1,17 +1,13 @@
|
||||
version: 2.1
|
||||
setup: true
|
||||
orbs:
|
||||
path-filtering: circleci/path-filtering@1.1.0
|
||||
path-filtering: circleci/path-filtering@0.0.1
|
||||
|
||||
workflows:
|
||||
version: 2.1
|
||||
generate-config:
|
||||
jobs:
|
||||
- path-filtering/filter:
|
||||
filters:
|
||||
tags:
|
||||
only:
|
||||
- /.*/
|
||||
base-revision: main
|
||||
config-path: .circleci/continue_config.yml
|
||||
mapping: |
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
[codespell]
|
||||
ignore-words-list = blong, afterall, assistent, crasher, requestor
|
||||
skip = ./.git,./gpt4all-chat/translations,*.pdf,*.svg,*.lock
|
||||
ignore-words-list = blong, afterall, som, assistent, crasher
|
||||
skip = .git,*.pdf,*.svg,*.lock,*.ts
|
||||
|
||||
1
.gitignore
vendored
@@ -182,7 +182,6 @@ gpt4all-chat/models/*
|
||||
build_*
|
||||
build-*
|
||||
cmake-build-*
|
||||
/gpt4all-chat/tests/python/config.py
|
||||
|
||||
# IntelliJ
|
||||
.idea/
|
||||
|
||||
12
.gitmodules
vendored
@@ -11,15 +11,3 @@
|
||||
[submodule "gpt4all-chat/deps/fmt"]
|
||||
path = gpt4all-chat/deps/fmt
|
||||
url = https://github.com/fmtlib/fmt.git
|
||||
[submodule "gpt4all-chat/deps/DuckX"]
|
||||
path = gpt4all-chat/deps/DuckX
|
||||
url = https://github.com/nomic-ai/DuckX.git
|
||||
[submodule "gpt4all-chat/deps/QXlsx"]
|
||||
path = gpt4all-chat/deps/QXlsx
|
||||
url = https://github.com/nomic-ai/QXlsx.git
|
||||
[submodule "gpt4all-chat/deps/minja"]
|
||||
path = gpt4all-chat/deps/minja
|
||||
url = https://github.com/nomic-ai/minja.git
|
||||
[submodule "gpt4all-chat/deps/json"]
|
||||
path = gpt4all-chat/deps/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
|
||||
@@ -51,6 +51,11 @@ Thiago Ramos ([@thiagojramos](https://github.com/thiagojramos))<br/>
|
||||
E-mail: thiagojramos@outlook.com<br/>
|
||||
- pt\_BR translation
|
||||
|
||||
Victor Emanuel ([@SINAPSA-IC](https://github.com/SINAPSA-IC))<br/>
|
||||
E-mail: contact@sinapsaro.ro<br/>
|
||||
Discord: `@sinapsa_ic_56124_99632`
|
||||
- ro\_RO translation
|
||||
|
||||
不知火 Shiranui ([@supersonictw](https://github.com/supersonictw))<br/>
|
||||
E-mail: supersonic@livemail.tw<br/>
|
||||
Discord: `@supersonictw`
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
Read about what's new in <a href="https://www.nomic.ai/blog/tag/gpt4all">our blog</a>.
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://nomic.ai/gpt4all/#newsletter-form">Subscribe to the newsletter</a>
|
||||
<a href="https://forms.nomic.ai/gpt4all-release-notes-signup">Subscribe to the newsletter</a>
|
||||
</p>
|
||||
|
||||
https://github.com/nomic-ai/gpt4all/assets/70534565/513a0f15-4964-4109-89e4-4f9a9011f311
|
||||
@@ -23,6 +23,9 @@ https://github.com/nomic-ai/gpt4all/assets/70534565/513a0f15-4964-4109-89e4-4f9a
|
||||
<p align="center">
|
||||
GPT4All is made possible by our compute partner <a href="https://www.paperspace.com/">Paperspace</a>.
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://www.phorm.ai/query?projectId=755eecd3-24ad-49cc-abf4-0ab84caacf63"><img src="https://img.shields.io/badge/Phorm-Ask_AI-%23F2777A.svg" alt="phorm.ai"></a>
|
||||
</p>
|
||||
|
||||
## Download Links
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
function(gpt4all_add_warning_options target)
|
||||
if (MSVC)
|
||||
return()
|
||||
endif()
|
||||
target_compile_options("${target}" PRIVATE
|
||||
# base options
|
||||
-Wall
|
||||
-Wextra
|
||||
# extra options
|
||||
-Wcast-align
|
||||
-Wextra-semi
|
||||
-Wformat=2
|
||||
-Wmissing-include-dirs
|
||||
-Wsuggest-override
|
||||
-Wvla
|
||||
# errors
|
||||
-Werror=format-security
|
||||
-Werror=init-self
|
||||
-Werror=pointer-arith
|
||||
-Werror=undef
|
||||
# disabled warnings
|
||||
-Wno-sign-compare
|
||||
-Wno-unused-parameter
|
||||
)
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
target_compile_options("${target}" PRIVATE
|
||||
-Wduplicated-branches
|
||||
-Wduplicated-cond
|
||||
-Wlogical-op
|
||||
-Wno-reorder
|
||||
-Wno-null-dereference
|
||||
)
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$")
|
||||
target_compile_options("${target}" PRIVATE
|
||||
-Wunreachable-code-break
|
||||
-Wunreachable-code-return
|
||||
-Werror=pointer-integer-compare
|
||||
-Wno-reorder-ctor
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -1,7 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.23) # for FILE_SET
|
||||
|
||||
include(../common/common.cmake)
|
||||
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
@@ -97,6 +94,8 @@ if (LLMODEL_ROCM)
|
||||
list(APPEND BUILD_VARIANTS rocm rocm-avxonly)
|
||||
endif()
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
|
||||
# Go through each build variant
|
||||
foreach(BUILD_VARIANT IN LISTS BUILD_VARIANTS)
|
||||
# Determine flags
|
||||
@@ -152,7 +151,6 @@ foreach(BUILD_VARIANT IN LISTS BUILD_VARIANTS)
|
||||
# Add each individual implementations
|
||||
add_library(llamamodel-mainline-${BUILD_VARIANT} SHARED
|
||||
src/llamamodel.cpp src/llmodel_shared.cpp)
|
||||
gpt4all_add_warning_options(llamamodel-mainline-${BUILD_VARIANT})
|
||||
target_compile_definitions(llamamodel-mainline-${BUILD_VARIANT} PRIVATE
|
||||
LLAMA_VERSIONS=>=3 LLAMA_DATE=999999)
|
||||
target_include_directories(llamamodel-mainline-${BUILD_VARIANT} PRIVATE
|
||||
@@ -171,7 +169,6 @@ add_library(llmodel
|
||||
src/llmodel_c.cpp
|
||||
src/llmodel_shared.cpp
|
||||
)
|
||||
gpt4all_add_warning_options(llmodel)
|
||||
target_sources(llmodel PUBLIC
|
||||
FILE_SET public_headers TYPE HEADERS BASE_DIRS include
|
||||
FILES include/gpt4all-backend/llmodel.h
|
||||
|
||||
@@ -27,7 +27,7 @@ Unfortunately, no for three reasons:
|
||||
|
||||
# What is being done to make them more compatible?
|
||||
|
||||
A few things. Number one, we are maintaining compatibility with our current model zoo by way of the submodule pinning. However, we are also exploring how we can update to newer versions of llama.cpp without breaking our current models. This might involve an additional magic header check or it could possibly involve keeping the currently pinned submodule and also adding a new submodule with later changes and differentiating them with namespaces or some other manner. Investigations continue.
|
||||
A few things. Number one, we are maintaining compatibility with our current model zoo by way of the submodule pinning. However, we are also exploring how we can update to newer versions of llama.cpp without breaking our current models. This might involve an additional magic header check or it could possibly involve keeping the currently pinned submodule and also adding a new submodule with later changes and differienting them with namespaces or some other manner. Investigations continue.
|
||||
|
||||
# What about GPU inference?
|
||||
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -25,10 +23,6 @@ using namespace std::string_literals;
|
||||
class LLModel {
|
||||
public:
|
||||
using Token = int32_t;
|
||||
using PromptCallback = std::function<bool(std::span<const Token> batch, bool cached)>;
|
||||
using ResponseCallback = std::function<bool(Token token, std::string_view piece)>;
|
||||
using EmbedCancelCallback = bool(unsigned *batchSizes, unsigned nBatch, const char *backend);
|
||||
using ProgressCallback = std::function<bool(float progress)>;
|
||||
|
||||
class BadArchError: public std::runtime_error {
|
||||
public:
|
||||
@@ -106,7 +100,6 @@ public:
|
||||
static int32_t maxContextLength(const std::string &modelPath);
|
||||
static int32_t layerCount(const std::string &modelPath);
|
||||
static bool isEmbeddingModel(const std::string &modelPath);
|
||||
static auto chatTemplate(const char *modelPath) -> std::expected<std::string, std::string>;
|
||||
static void setImplementationsSearchPath(const std::string &path);
|
||||
static const std::string &implementationsSearchPath();
|
||||
static bool hasSupportedCPU();
|
||||
@@ -130,6 +123,9 @@ public:
|
||||
};
|
||||
|
||||
struct PromptContext {
|
||||
std::vector<int32_t> tokens; // current tokens in the context window
|
||||
int32_t n_past = 0; // number of tokens in past conversation
|
||||
int32_t n_ctx = 0; // number of tokens possible in context window
|
||||
int32_t n_predict = 200;
|
||||
int32_t top_k = 40;
|
||||
float top_p = 0.9f;
|
||||
@@ -141,28 +137,34 @@ public:
|
||||
float contextErase = 0.5f; // percent of context to erase if we exceed the context window
|
||||
};
|
||||
|
||||
using ProgressCallback = std::function<bool(float progress)>;
|
||||
|
||||
explicit LLModel() {}
|
||||
virtual ~LLModel() {}
|
||||
|
||||
virtual bool supportsEmbedding() const = 0;
|
||||
virtual bool supportsCompletion() const = 0;
|
||||
virtual bool loadModel(const std::string &modelPath, int n_ctx, int ngl) = 0;
|
||||
virtual bool isModelBlacklisted(const std::string &modelPath) const { (void)modelPath; return false; }
|
||||
virtual bool isModelBlacklisted(const std::string &modelPath) const { (void)modelPath; return false; };
|
||||
virtual bool isEmbeddingModel(const std::string &modelPath) const { (void)modelPath; return false; }
|
||||
virtual bool isModelLoaded() const = 0;
|
||||
virtual size_t requiredMem(const std::string &modelPath, int n_ctx, int ngl) = 0;
|
||||
virtual size_t stateSize() const = 0;
|
||||
virtual size_t saveState(std::span<uint8_t> stateOut, std::vector<Token> &inputTokensOut) const = 0;
|
||||
virtual size_t restoreState(std::span<const uint8_t> state, std::span<const Token> inputTokens) = 0;
|
||||
virtual size_t stateSize() const { return 0; }
|
||||
virtual size_t saveState(uint8_t *dest) const { (void)dest; return 0; }
|
||||
virtual size_t restoreState(const uint8_t *src) { (void)src; return 0; }
|
||||
|
||||
// This method requires the model to return true from supportsCompletion otherwise it will throw
|
||||
// an error
|
||||
virtual void prompt(std::string_view prompt,
|
||||
const PromptCallback &promptCallback,
|
||||
const ResponseCallback &responseCallback,
|
||||
const PromptContext &ctx);
|
||||
virtual void prompt(const std::string &prompt,
|
||||
const std::string &promptTemplate,
|
||||
std::function<bool(int32_t)> promptCallback,
|
||||
std::function<bool(int32_t, const std::string&)> responseCallback,
|
||||
bool allowContextShift,
|
||||
PromptContext &ctx,
|
||||
bool special = false,
|
||||
std::optional<std::string_view> fakeReply = {});
|
||||
|
||||
virtual int32_t countPromptTokens(std::string_view prompt) const;
|
||||
using EmbedCancelCallback = bool(unsigned *batchSizes, unsigned nBatch, const char *backend);
|
||||
|
||||
virtual size_t embeddingSize() const {
|
||||
throw std::logic_error(std::string(implementation().modelType()) + " does not support embeddings");
|
||||
@@ -207,24 +209,16 @@ public:
|
||||
|
||||
void setProgressCallback(ProgressCallback callback) { m_progressCallback = callback; }
|
||||
|
||||
virtual int32_t contextLength() const = 0;
|
||||
virtual auto specialTokens() -> std::unordered_map<std::string, std::string> const = 0;
|
||||
|
||||
protected:
|
||||
// These are pure virtual because subclasses need to implement as the default implementation of
|
||||
// 'prompt' above calls these functions
|
||||
virtual std::vector<Token> tokenize(std::string_view str) const = 0;
|
||||
virtual std::vector<Token> tokenize(PromptContext &ctx, std::string_view str, bool special = false) = 0;
|
||||
virtual bool isSpecialToken(Token id) const = 0;
|
||||
virtual std::string tokenToString(Token id) const = 0;
|
||||
virtual void initSampler(const PromptContext &ctx) = 0;
|
||||
virtual Token sampleToken() const = 0;
|
||||
virtual bool evalTokens(int32_t nPast, std::span<const Token> tokens) const = 0;
|
||||
virtual void shiftContext(const PromptContext &promptCtx, int32_t *nPast) = 0;
|
||||
virtual int32_t inputLength() const = 0;
|
||||
virtual int32_t computeModelInputPosition(std::span<const Token> input) const = 0;
|
||||
virtual void setModelInputPosition(int32_t pos) = 0;
|
||||
virtual void appendInputToken(Token tok) = 0;
|
||||
virtual std::span<const Token> inputTokens() const = 0;
|
||||
virtual Token sampleToken(PromptContext &ctx) const = 0;
|
||||
virtual bool evalTokens(PromptContext &ctx, const std::vector<int32_t> &tokens) const = 0;
|
||||
virtual void shiftContext(PromptContext &promptCtx) = 0;
|
||||
virtual int32_t contextLength() const = 0;
|
||||
virtual const std::vector<Token> &endTokens() const = 0;
|
||||
virtual bool shouldAddBOS() const = 0;
|
||||
|
||||
@@ -240,12 +234,6 @@ protected:
|
||||
return -1;
|
||||
}
|
||||
|
||||
virtual auto chatTemplate(const char *modelPath) const -> std::expected<std::string, std::string>
|
||||
{
|
||||
(void)modelPath;
|
||||
return std::unexpected("not implemented");
|
||||
}
|
||||
|
||||
const Implementation *m_implementation = nullptr;
|
||||
|
||||
ProgressCallback m_progressCallback;
|
||||
@@ -257,15 +245,17 @@ protected:
|
||||
return true;
|
||||
}
|
||||
|
||||
// prefill context with prompt
|
||||
auto decodePrompt(const PromptCallback &promptCallback,
|
||||
const PromptContext &promptCtx,
|
||||
std::vector<Token> embd_inp)
|
||||
-> std::optional<int32_t>;
|
||||
// generate a response
|
||||
void generateResponse(const ResponseCallback &responseCallback,
|
||||
const PromptContext &promptCtx,
|
||||
int32_t nPast);
|
||||
bool decodePrompt(std::function<bool(int32_t)> promptCallback,
|
||||
std::function<bool(int32_t, const std::string&)> responseCallback,
|
||||
bool allowContextShift,
|
||||
PromptContext &promptCtx,
|
||||
std::vector<Token> embd_inp,
|
||||
bool isResponse = false);
|
||||
void generateResponse(std::function<bool(int32_t, const std::string&)> responseCallback,
|
||||
bool allowContextShift,
|
||||
PromptContext &promptCtx);
|
||||
|
||||
Token m_tokenize_last_token = -1; // not serialized
|
||||
|
||||
friend class LLMImplementation;
|
||||
};
|
||||
|
||||
@@ -23,11 +23,6 @@ extern "C" {
|
||||
*/
|
||||
typedef void *llmodel_model;
|
||||
|
||||
/**
|
||||
* A token.
|
||||
*/
|
||||
typedef int32_t token_t;
|
||||
|
||||
/**
|
||||
* llmodel_prompt_context structure for holding the prompt context.
|
||||
* NOTE: The implementation takes care of all the memory handling of the raw logits pointer and the
|
||||
@@ -35,15 +30,19 @@ typedef int32_t token_t;
|
||||
* behavior.
|
||||
*/
|
||||
struct llmodel_prompt_context {
|
||||
int32_t *tokens; // current tokens in the context window
|
||||
size_t tokens_size; // the size of the raw tokens vector
|
||||
int32_t n_past; // number of tokens in past conversation
|
||||
int32_t n_ctx; // number of tokens possible in context window
|
||||
int32_t n_predict; // number of tokens to predict
|
||||
int32_t top_k; // top k logits to sample from
|
||||
float top_p; // nucleus sampling probability threshold
|
||||
float min_p; // Min P sampling
|
||||
float temp; // temperature to adjust model's output distribution
|
||||
float top_p; // nucleus sampling probability threshold
|
||||
float min_p; // Min P sampling
|
||||
float temp; // temperature to adjust model's output distribution
|
||||
int32_t n_batch; // number of predictions to generate in parallel
|
||||
float repeat_penalty; // penalty factor for repeated tokens
|
||||
float repeat_penalty; // penalty factor for repeated tokens
|
||||
int32_t repeat_last_n; // last n tokens to penalize
|
||||
float context_erase; // percent of context to erase if we exceed the context window
|
||||
float context_erase; // percent of context to erase if we exceed the context window
|
||||
};
|
||||
|
||||
struct llmodel_gpu_device {
|
||||
@@ -62,12 +61,10 @@ typedef struct llmodel_gpu_device llmodel_gpu_device;
|
||||
|
||||
/**
|
||||
* Callback type for prompt processing.
|
||||
* @param token_ids An array of token ids of the prompt.
|
||||
* @param n_token_ids The number of tokens in the array.
|
||||
* @param cached Whether the tokens were already in cache.
|
||||
* @param token_id The token id of the prompt.
|
||||
* @return a bool indicating whether the model should keep processing.
|
||||
*/
|
||||
typedef bool (*llmodel_prompt_callback)(const token_t *token_ids, size_t n_token_ids, bool cached);
|
||||
typedef bool (*llmodel_prompt_callback)(int32_t token_id);
|
||||
|
||||
/**
|
||||
* Callback type for response.
|
||||
@@ -75,7 +72,7 @@ typedef bool (*llmodel_prompt_callback)(const token_t *token_ids, size_t n_token
|
||||
* @param response The response string. NOTE: a token_id of -1 indicates the string is an error string.
|
||||
* @return a bool indicating whether the model should keep generating.
|
||||
*/
|
||||
typedef bool (*llmodel_response_callback)(token_t token_id, const char *response);
|
||||
typedef bool (*llmodel_response_callback)(int32_t token_id, const char *response);
|
||||
|
||||
/**
|
||||
* Embedding cancellation callback for use with llmodel_embed.
|
||||
@@ -86,8 +83,6 @@ typedef bool (*llmodel_response_callback)(token_t token_id, const char *response
|
||||
*/
|
||||
typedef bool (*llmodel_emb_cancel_callback)(unsigned *batch_sizes, unsigned n_batch, const char *backend);
|
||||
|
||||
typedef void (*llmodel_special_token_callback)(const char *name, const char *token);
|
||||
|
||||
/**
|
||||
* Create a llmodel instance.
|
||||
* Recognises correct model type from file at model_path
|
||||
@@ -146,57 +141,46 @@ bool llmodel_isModelLoaded(llmodel_model model);
|
||||
* @param model A pointer to the llmodel_model instance.
|
||||
* @return the size in bytes of the internal state of the model
|
||||
*/
|
||||
uint64_t llmodel_state_get_size(llmodel_model model);
|
||||
uint64_t llmodel_get_state_size(llmodel_model model);
|
||||
|
||||
/**
|
||||
* Saves the internal state of the model.
|
||||
* Saves the internal state of the model to the specified destination address.
|
||||
* NOTE: This state data is specific to the type of model you have created.
|
||||
* @param model A pointer to the llmodel_model instance.
|
||||
* @param state Where to store the state. This must be a buffer of at least llmodel_state_get_size() bytes.
|
||||
* @param state_size The size of the destination for the state.
|
||||
* @param input_tokens_out Where to store the address of the token cache state. This is dynamically allocated and must
|
||||
* be freed with llmodel_state_free_input_tokens.
|
||||
* @param n_input_tokens Where to store the size of the token cache state.
|
||||
* @return The number of bytes copied. On error, zero is returned, the token cache is set to NULL, and the token cache
|
||||
* size is set to zero.
|
||||
* @param dest A pointer to the destination.
|
||||
* @return the number of bytes copied
|
||||
*/
|
||||
uint64_t llmodel_state_get_data(llmodel_model model, uint8_t *state_out, uint64_t state_size,
|
||||
token_t **input_tokens_out, uint64_t *n_input_tokens);
|
||||
|
||||
/**
|
||||
* Frees the temporary token cache buffer created by a call to llmodel_state_get_data().
|
||||
* @param input_tokens The token cache buffer.
|
||||
*/
|
||||
void llmodel_state_free_input_tokens(token_t *input_tokens);
|
||||
uint64_t llmodel_save_state_data(llmodel_model model, uint8_t *dest);
|
||||
|
||||
/**
|
||||
* Restores the internal state of the model using data from the specified address.
|
||||
* NOTE: This state data is specific to the type of model you have created.
|
||||
* @param model A pointer to the llmodel_model instance.
|
||||
* @param state A pointer to the state data.
|
||||
* @param state_size The size of the state data.
|
||||
* @param input_tokens The token cache associated with the saved state.
|
||||
* @param n_input_tokens The number of tokens in input_tokens.
|
||||
* @return The number of bytes read, or zero on error.
|
||||
* @param src A pointer to the src.
|
||||
* @return the number of bytes read
|
||||
*/
|
||||
uint64_t llmodel_state_set_data(llmodel_model model, const uint8_t *state, uint64_t state_size,
|
||||
const token_t *input_tokens, uint64_t n_input_tokens);
|
||||
uint64_t llmodel_restore_state_data(llmodel_model model, const uint8_t *src);
|
||||
|
||||
/**
|
||||
* Generate a response using the model.
|
||||
* @param model A pointer to the llmodel_model instance.
|
||||
* @param prompt A string representing the input prompt.
|
||||
* @param prompt_template A string representing the input prompt template.
|
||||
* @param prompt_callback A callback function for handling the processing of prompt.
|
||||
* @param response_callback A callback function for handling the generated response.
|
||||
* @param allow_context_shift Whether to allow shifting of context to make room for more input.
|
||||
* @param special True if special tokens in the prompt should be processed, false otherwise.
|
||||
* @param fake_reply A string to insert into context as the model's reply, or NULL to generate one.
|
||||
* @param ctx A pointer to the llmodel_prompt_context structure.
|
||||
* @param error A pointer to a string; will only be set on error.
|
||||
*/
|
||||
bool llmodel_prompt(llmodel_model model,
|
||||
const char *prompt,
|
||||
llmodel_prompt_callback prompt_callback,
|
||||
llmodel_response_callback response_callback,
|
||||
llmodel_prompt_context *ctx,
|
||||
const char **error);
|
||||
void llmodel_prompt(llmodel_model model, const char *prompt,
|
||||
const char *prompt_template,
|
||||
llmodel_prompt_callback prompt_callback,
|
||||
llmodel_response_callback response_callback,
|
||||
bool allow_context_shift,
|
||||
llmodel_prompt_context *ctx,
|
||||
bool special,
|
||||
const char *fake_reply);
|
||||
|
||||
/**
|
||||
* Generate an embedding using the model.
|
||||
@@ -308,10 +292,6 @@ const char *llmodel_model_backend_name(llmodel_model model);
|
||||
*/
|
||||
const char *llmodel_model_gpu_device_name(llmodel_model model);
|
||||
|
||||
int32_t llmodel_count_prompt_tokens(llmodel_model model, const char *prompt, const char **error);
|
||||
|
||||
void llmodel_model_foreach_special_token(llmodel_model model, llmodel_special_token_callback callback);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -978,13 +978,10 @@ function(include_ggml SUFFIX)
|
||||
|
||||
add_library(llama${SUFFIX} STATIC
|
||||
${DIRECTORY}/include/llama.h
|
||||
${DIRECTORY}/src/llama-grammar.cpp
|
||||
${DIRECTORY}/src/llama-sampling.cpp
|
||||
${DIRECTORY}/src/llama-vocab.cpp
|
||||
${DIRECTORY}/src/llama.cpp
|
||||
${DIRECTORY}/src/unicode-data.cpp
|
||||
${DIRECTORY}/src/unicode.cpp
|
||||
${DIRECTORY}/src/unicode.h
|
||||
${DIRECTORY}/src/unicode.cpp
|
||||
${DIRECTORY}/src/unicode-data.cpp
|
||||
)
|
||||
|
||||
target_include_directories(llama${SUFFIX} PUBLIC ${DIRECTORY}/include ${DIRECTORY}/ggml/include)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "llamamodel_impl.h"
|
||||
|
||||
#include "llmodel.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <ggml.h>
|
||||
#include <llama.h>
|
||||
@@ -104,34 +103,26 @@ static bool llama_verbose()
|
||||
return var && *var;
|
||||
}
|
||||
|
||||
static void llama_log_callback(ggml_log_level level, const char *text, void *userdata, bool warn)
|
||||
static void llama_log_callback(enum ggml_log_level level, const char *text, void *userdata)
|
||||
{
|
||||
(void)userdata;
|
||||
|
||||
static ggml_log_level lastlevel = GGML_LOG_LEVEL_NONE;
|
||||
if (!llama_verbose()) {
|
||||
auto efflevel = level == GGML_LOG_LEVEL_CONT ? lastlevel : level;
|
||||
lastlevel = efflevel;
|
||||
switch (efflevel) {
|
||||
case GGML_LOG_LEVEL_CONT:
|
||||
UNREACHABLE();
|
||||
break;
|
||||
case GGML_LOG_LEVEL_WARN:
|
||||
if (warn) break;
|
||||
[[fallthrough]];
|
||||
case GGML_LOG_LEVEL_NONE: // not used?
|
||||
case GGML_LOG_LEVEL_INFO:
|
||||
case GGML_LOG_LEVEL_DEBUG:
|
||||
return; // suppress
|
||||
case GGML_LOG_LEVEL_ERROR:
|
||||
;
|
||||
}
|
||||
if (llama_verbose() || level <= GGML_LOG_LEVEL_ERROR) {
|
||||
fputs(text, stderr);
|
||||
}
|
||||
|
||||
fputs(text, stderr);
|
||||
}
|
||||
|
||||
#ifdef GGML_USE_CUDA
|
||||
static void cuda_log_callback(enum ggml_log_level level, const char *text, void *userdata)
|
||||
{
|
||||
(void)userdata;
|
||||
if (llama_verbose() || level <= GGML_LOG_LEVEL_WARN) {
|
||||
fputs(text, stderr);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
struct gpt_params {
|
||||
int32_t seed = -1; // RNG seed
|
||||
int32_t n_keep = 0; // number of tokens to keep from initial prompt
|
||||
|
||||
// sampling parameters
|
||||
@@ -146,6 +137,44 @@ struct gpt_params {
|
||||
bool use_mlock = false; // use mlock to keep model in memory
|
||||
};
|
||||
|
||||
static llama_token llama_sample_top_p_top_k(
|
||||
llama_context *ctx,
|
||||
const llama_token *last_n_tokens_data,
|
||||
int last_n_tokens_size,
|
||||
int top_k,
|
||||
float top_p,
|
||||
float min_p,
|
||||
float temp,
|
||||
float repeat_penalty) {
|
||||
auto logits = llama_get_logits_ith(ctx, -1);
|
||||
auto n_vocab = llama_n_vocab(llama_get_model(ctx));
|
||||
// Populate initial list of all candidates
|
||||
std::vector<llama_token_data> candidates;
|
||||
candidates.reserve(n_vocab);
|
||||
for (int token_id = 0; token_id < n_vocab; token_id++) {
|
||||
candidates.emplace_back(llama_token_data{token_id, logits[token_id], 0.0f});
|
||||
}
|
||||
llama_token_data_array candidates_p = {candidates.data(), candidates.size(), false};
|
||||
// Sample repeat penalty
|
||||
llama_sample_repetition_penalties(nullptr, &candidates_p, last_n_tokens_data, last_n_tokens_size, repeat_penalty, 0.0f, 0.0f);
|
||||
|
||||
llama_token id;
|
||||
if (temp == 0.0) {
|
||||
// greedy sampling, no probs
|
||||
id = llama_sample_token_greedy(ctx, &candidates_p);
|
||||
} else {
|
||||
// temperature sampling
|
||||
llama_sample_top_k(ctx, &candidates_p, top_k, 1);
|
||||
llama_sample_tail_free(ctx, &candidates_p, 1.0f, 1);
|
||||
llama_sample_typical(ctx, &candidates_p, 1.0f, 1);
|
||||
llama_sample_top_p(ctx, &candidates_p, top_p, 1);
|
||||
llama_sample_min_p(ctx, &candidates_p, min_p, 1);
|
||||
llama_sample_temp(ctx, &candidates_p, temp);
|
||||
id = llama_sample_token(ctx, &candidates_p);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
const char *get_arch_name(gguf_context *ctx_gguf)
|
||||
{
|
||||
const int kid = gguf_find_key(ctx_gguf, "general.architecture");
|
||||
@@ -202,7 +231,7 @@ static int32_t get_arch_key_u32(std::string const &modelPath, std::string const
|
||||
if (keyidx != -1) {
|
||||
value = gguf_get_val_u32(ctx, keyidx);
|
||||
} else {
|
||||
std::cerr << __func__ << ": " << key << " not found in " << modelPath << "\n";
|
||||
std::cerr << __func__ << ": " << key << "not found in " << modelPath << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,27 +241,21 @@ cleanup:
|
||||
}
|
||||
|
||||
struct LLamaPrivate {
|
||||
bool modelLoaded = false;
|
||||
int device = -1;
|
||||
std::string deviceName;
|
||||
int64_t n_threads = 0;
|
||||
std::vector<LLModel::Token> end_tokens;
|
||||
const char *backend_name = nullptr;
|
||||
std::vector<LLModel::Token> inputTokens;
|
||||
|
||||
llama_model *model = nullptr;
|
||||
llama_context *ctx = nullptr;
|
||||
llama_model_params model_params;
|
||||
llama_context_params ctx_params;
|
||||
llama_sampler *sampler_chain;
|
||||
const std::string modelPath;
|
||||
bool modelLoaded = false;
|
||||
int device = -1;
|
||||
std::string deviceName;
|
||||
llama_model *model = nullptr;
|
||||
llama_context *ctx = nullptr;
|
||||
llama_model_params model_params;
|
||||
llama_context_params ctx_params;
|
||||
int64_t n_threads = 0;
|
||||
std::vector<LLModel::Token> end_tokens;
|
||||
const char *backend_name = nullptr;
|
||||
};
|
||||
|
||||
LLamaModel::LLamaModel()
|
||||
: d_ptr(std::make_unique<LLamaPrivate>())
|
||||
{
|
||||
auto sparams = llama_sampler_chain_default_params();
|
||||
d_ptr->sampler_chain = llama_sampler_chain_init(sparams);
|
||||
}
|
||||
: d_ptr(new LLamaPrivate) {}
|
||||
|
||||
// default hparams (LLaMA 7B)
|
||||
struct llama_file_hparams {
|
||||
@@ -421,9 +444,10 @@ bool LLamaModel::loadModel(const std::string &modelPath, int n_ctx, int ngl)
|
||||
}
|
||||
}
|
||||
|
||||
d_ptr->ctx_params.n_ctx = n_ctx;
|
||||
d_ptr->ctx_params.type_k = params.kv_type;
|
||||
d_ptr->ctx_params.type_v = params.kv_type;
|
||||
d_ptr->ctx_params.n_ctx = n_ctx;
|
||||
d_ptr->ctx_params.seed = params.seed;
|
||||
d_ptr->ctx_params.type_k = params.kv_type;
|
||||
d_ptr->ctx_params.type_v = params.kv_type;
|
||||
|
||||
// The new batch API provides space for n_vocab*n_tokens logits. Tell llama.cpp early
|
||||
// that we want this many logits so the state serializes consistently.
|
||||
@@ -489,7 +513,6 @@ LLamaModel::~LLamaModel()
|
||||
llama_free(d_ptr->ctx);
|
||||
}
|
||||
llama_free_model(d_ptr->model);
|
||||
llama_sampler_free(d_ptr->sampler_chain);
|
||||
}
|
||||
|
||||
bool LLamaModel::isModelLoaded() const
|
||||
@@ -499,32 +522,32 @@ bool LLamaModel::isModelLoaded() const
|
||||
|
||||
size_t LLamaModel::stateSize() const
|
||||
{
|
||||
return llama_state_get_size(d_ptr->ctx);
|
||||
return llama_get_state_size(d_ptr->ctx);
|
||||
}
|
||||
|
||||
size_t LLamaModel::saveState(std::span<uint8_t> stateOut, std::vector<Token> &inputTokensOut) const
|
||||
size_t LLamaModel::saveState(uint8_t *dest) const
|
||||
{
|
||||
size_t bytesWritten = llama_state_get_data(d_ptr->ctx, stateOut.data(), stateOut.size());
|
||||
if (bytesWritten)
|
||||
inputTokensOut.assign(d_ptr->inputTokens.begin(), d_ptr->inputTokens.end());
|
||||
return bytesWritten;
|
||||
return llama_copy_state_data(d_ptr->ctx, dest);
|
||||
}
|
||||
|
||||
size_t LLamaModel::restoreState(std::span<const uint8_t> state, std::span<const Token> inputTokens)
|
||||
size_t LLamaModel::restoreState(const uint8_t *src)
|
||||
{
|
||||
size_t bytesRead = llama_state_set_data(d_ptr->ctx, state.data(), state.size());
|
||||
if (bytesRead)
|
||||
d_ptr->inputTokens.assign(inputTokens.begin(), inputTokens.end());
|
||||
return bytesRead;
|
||||
// const_cast is required, see: https://github.com/ggerganov/llama.cpp/pull/1540
|
||||
return llama_set_state_data(d_ptr->ctx, const_cast<uint8_t*>(src));
|
||||
}
|
||||
|
||||
std::vector<LLModel::Token> LLamaModel::tokenize(std::string_view str) const
|
||||
std::vector<LLModel::Token> LLamaModel::tokenize(PromptContext &ctx, std::string_view str, bool special)
|
||||
{
|
||||
bool atStart = m_tokenize_last_token == -1;
|
||||
bool insertSpace = atStart || isSpecialToken(m_tokenize_last_token);
|
||||
std::vector<LLModel::Token> fres(str.length() + 4);
|
||||
int32_t fres_len = llama_tokenize(
|
||||
d_ptr->model, str.data(), str.length(), fres.data(), fres.size(), /*add_special*/ true, /*parse_special*/ true
|
||||
int32_t fres_len = llama_tokenize_gpt4all(
|
||||
d_ptr->model, str.data(), str.length(), fres.data(), fres.size(), /*add_special*/ atStart,
|
||||
/*parse_special*/ special, /*insert_space*/ insertSpace
|
||||
);
|
||||
fres.resize(fres_len);
|
||||
if (fres_len)
|
||||
m_tokenize_last_token = fres.back();
|
||||
return fres;
|
||||
}
|
||||
|
||||
@@ -550,58 +573,18 @@ std::string LLamaModel::tokenToString(Token id) const
|
||||
return std::string(result.data(), result.size());
|
||||
}
|
||||
|
||||
void LLamaModel::initSampler(const PromptContext &promptCtx)
|
||||
LLModel::Token LLamaModel::sampleToken(PromptContext &promptCtx) const
|
||||
{
|
||||
auto *model = d_ptr->model;
|
||||
auto *chain = d_ptr->sampler_chain;
|
||||
|
||||
// clear sampler chain
|
||||
for (int i = llama_sampler_chain_n(chain) - 1; i >= 0; i--) {
|
||||
auto *smpl = llama_sampler_chain_remove(chain, i);
|
||||
llama_sampler_free(smpl);
|
||||
}
|
||||
|
||||
// build new chain
|
||||
llama_sampler_chain_add(chain,
|
||||
llama_sampler_init_penalties(
|
||||
llama_n_vocab(model),
|
||||
llama_token_eos(model),
|
||||
llama_token_nl(model),
|
||||
promptCtx.repeat_last_n,
|
||||
promptCtx.repeat_penalty,
|
||||
// TODO(jared): consider making the below configurable
|
||||
/*penalty_freq*/ 0.0f,
|
||||
/*penalty_present*/ 0.0f,
|
||||
/*penalize_nl*/ true,
|
||||
/*ignore_eos*/ false
|
||||
)
|
||||
);
|
||||
if (promptCtx.temp == 0.0f) {
|
||||
llama_sampler_chain_add(chain, llama_sampler_init_greedy());
|
||||
} else {
|
||||
struct llama_sampler *samplers[] = {
|
||||
llama_sampler_init_top_k(promptCtx.top_k),
|
||||
llama_sampler_init_top_p(promptCtx.top_p, 1),
|
||||
llama_sampler_init_min_p(promptCtx.min_p, 1),
|
||||
llama_sampler_init_temp(promptCtx.temp),
|
||||
llama_sampler_init_softmax(),
|
||||
llama_sampler_init_dist(LLAMA_DEFAULT_SEED),
|
||||
};
|
||||
for (auto *smpl : samplers)
|
||||
llama_sampler_chain_add(chain, smpl);
|
||||
}
|
||||
const size_t n_prev_toks = std::min((size_t) promptCtx.repeat_last_n, promptCtx.tokens.size());
|
||||
return llama_sample_top_p_top_k(d_ptr->ctx,
|
||||
promptCtx.tokens.data() + promptCtx.tokens.size() - n_prev_toks,
|
||||
n_prev_toks, promptCtx.top_k, promptCtx.top_p, promptCtx.min_p, promptCtx.temp,
|
||||
promptCtx.repeat_penalty);
|
||||
}
|
||||
|
||||
LLModel::Token LLamaModel::sampleToken() const
|
||||
bool LLamaModel::evalTokens(PromptContext &ctx, const std::vector<int32_t> &tokens) const
|
||||
{
|
||||
return llama_sampler_sample(d_ptr->sampler_chain, d_ptr->ctx, -1);
|
||||
}
|
||||
|
||||
bool LLamaModel::evalTokens(int32_t nPast, std::span<const Token> tokens) const
|
||||
{
|
||||
assert(!tokens.empty());
|
||||
|
||||
llama_kv_cache_seq_rm(d_ptr->ctx, 0, nPast, -1);
|
||||
llama_kv_cache_seq_rm(d_ptr->ctx, 0, ctx.n_past, -1);
|
||||
|
||||
llama_batch batch = llama_batch_init(tokens.size(), 0, 1);
|
||||
|
||||
@@ -609,7 +592,7 @@ bool LLamaModel::evalTokens(int32_t nPast, std::span<const Token> tokens) const
|
||||
|
||||
for (int32_t i = 0; i < batch.n_tokens; i++) {
|
||||
batch.token [i] = tokens[i];
|
||||
batch.pos [i] = nPast + i;
|
||||
batch.pos [i] = ctx.n_past + i;
|
||||
batch.n_seq_id[i] = 1;
|
||||
batch.seq_id [i][0] = 0;
|
||||
batch.logits [i] = false;
|
||||
@@ -623,14 +606,14 @@ bool LLamaModel::evalTokens(int32_t nPast, std::span<const Token> tokens) const
|
||||
return res == 0;
|
||||
}
|
||||
|
||||
void LLamaModel::shiftContext(const PromptContext &promptCtx, int32_t *nPast)
|
||||
void LLamaModel::shiftContext(PromptContext &promptCtx)
|
||||
{
|
||||
// infinite text generation via context shifting
|
||||
|
||||
// erase up to n_ctx*contextErase tokens
|
||||
int n_keep = shouldAddBOS();
|
||||
int n_past = *nPast;
|
||||
int n_discard = std::min(n_past - n_keep, int(contextLength() * promptCtx.contextErase));
|
||||
int n_past = promptCtx.n_past;
|
||||
int n_discard = std::min(n_past - n_keep, int(promptCtx.n_ctx * promptCtx.contextErase));
|
||||
|
||||
assert(n_discard > 0);
|
||||
if (n_discard <= 0)
|
||||
@@ -643,9 +626,8 @@ void LLamaModel::shiftContext(const PromptContext &promptCtx, int32_t *nPast)
|
||||
llama_kv_cache_seq_rm (d_ptr->ctx, 0, n_keep, n_keep + n_discard);
|
||||
llama_kv_cache_seq_add(d_ptr->ctx, 0, n_keep + n_discard, n_past, -n_discard);
|
||||
|
||||
auto &inp = d_ptr->inputTokens;
|
||||
inp.erase(inp.begin() + n_keep, inp.begin() + n_keep + n_discard);
|
||||
*nPast = inp.size();
|
||||
promptCtx.tokens.erase(promptCtx.tokens.begin() + n_keep, promptCtx.tokens.begin() + n_keep + n_discard);
|
||||
promptCtx.n_past = promptCtx.tokens.size();
|
||||
}
|
||||
|
||||
int32_t LLamaModel::contextLength() const
|
||||
@@ -653,56 +635,6 @@ int32_t LLamaModel::contextLength() const
|
||||
return llama_n_ctx(d_ptr->ctx);
|
||||
}
|
||||
|
||||
auto LLamaModel::specialTokens() -> std::unordered_map<std::string, std::string> const
|
||||
{
|
||||
if (!d_ptr->model)
|
||||
throw std::logic_error("model not loaded");
|
||||
|
||||
std::unordered_map<std::string, std::string> tokens;
|
||||
if (auto id = llama_token_bos(d_ptr->model); id != LLAMA_TOKEN_NULL)
|
||||
tokens.emplace("bos_token", tokenToString(id));
|
||||
if (auto id = llama_token_eos(d_ptr->model); id != LLAMA_TOKEN_NULL)
|
||||
tokens.emplace("eos_token", tokenToString(id));
|
||||
return tokens;
|
||||
}
|
||||
|
||||
int32_t LLamaModel::inputLength() const
|
||||
{
|
||||
return d_ptr->inputTokens.size();
|
||||
}
|
||||
|
||||
int32_t LLamaModel::computeModelInputPosition(std::span<const Token> input) const
|
||||
{
|
||||
// find common prefix
|
||||
auto cacheIt = d_ptr->inputTokens.begin();
|
||||
auto inputIt = input.begin();
|
||||
while (cacheIt < d_ptr->inputTokens.end() && inputIt < input.end() && *cacheIt == *inputIt) {
|
||||
++cacheIt; ++inputIt;
|
||||
}
|
||||
// tell the caller to ignore the tokens between [begin, inputIt)
|
||||
return inputIt - input.begin();
|
||||
}
|
||||
|
||||
void LLamaModel::setModelInputPosition(int32_t pos)
|
||||
{
|
||||
auto &inp = d_ptr->inputTokens;
|
||||
assert(pos >= 0);
|
||||
assert(pos <= inp.size());
|
||||
// truncate token cache to end at the new n_past
|
||||
if (pos < inp.size())
|
||||
inp.resize(pos);
|
||||
}
|
||||
|
||||
void LLamaModel::appendInputToken(Token tok)
|
||||
{
|
||||
d_ptr->inputTokens.push_back(tok);
|
||||
}
|
||||
|
||||
auto LLamaModel::inputTokens() const -> std::span<const Token>
|
||||
{
|
||||
return d_ptr->inputTokens;
|
||||
}
|
||||
|
||||
const std::vector<LLModel::Token> &LLamaModel::endTokens() const
|
||||
{
|
||||
return d_ptr->end_tokens;
|
||||
@@ -723,37 +655,6 @@ int32_t LLamaModel::layerCount(std::string const &modelPath) const
|
||||
return get_arch_key_u32(modelPath, "block_count");
|
||||
}
|
||||
|
||||
// TODO(jared): reduce redundant code and operations by combining all metadata getters for unloaded
|
||||
// models into a class that keeps the model file open
|
||||
auto LLamaModel::chatTemplate(const char *modelPath) const -> std::expected<std::string, std::string>
|
||||
{
|
||||
auto *ctx = load_gguf(modelPath);
|
||||
if (!ctx)
|
||||
return std::unexpected("failed to open model file");
|
||||
|
||||
std::expected<std::string, std::string> result;
|
||||
enum gguf_type ktype;
|
||||
const int kid = gguf_find_key(ctx, "tokenizer.chat_template");
|
||||
if (kid == -1) {
|
||||
result = std::unexpected("key not found");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ktype = gguf_get_kv_type(ctx, kid);
|
||||
if (ktype != GGUF_TYPE_STRING) {
|
||||
result = std::unexpected(
|
||||
"expected key type STRING (" + std::to_string(GGUF_TYPE_STRING) + "), got " + std::to_string(ktype)
|
||||
);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
result = gguf_get_val_str(ctx, kid);
|
||||
|
||||
cleanup:
|
||||
gguf_free(ctx);
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef GGML_USE_VULKAN
|
||||
static const char *getVulkanVendorName(uint32_t vendorID)
|
||||
{
|
||||
@@ -1326,9 +1227,9 @@ DLL_EXPORT bool is_arch_supported(const char *arch)
|
||||
|
||||
DLL_EXPORT LLModel *construct()
|
||||
{
|
||||
llama_log_set([](auto l, auto t, auto u) { llama_log_callback(l, t, u, false); }, nullptr);
|
||||
llama_log_set(llama_log_callback, nullptr);
|
||||
#ifdef GGML_USE_CUDA
|
||||
ggml_backend_cuda_log_set_callback([](auto l, auto t, auto u) { llama_log_callback(l, t, u, true); }, nullptr);
|
||||
ggml_backend_cuda_log_set_callback(cuda_log_callback, nullptr);
|
||||
#endif
|
||||
return new LLamaModel;
|
||||
}
|
||||
|
||||
@@ -7,11 +7,9 @@
|
||||
#include "llmodel.h"
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
struct LLamaPrivate;
|
||||
struct EmbModelSpec;
|
||||
@@ -29,8 +27,8 @@ public:
|
||||
bool isModelLoaded() const override;
|
||||
size_t requiredMem(const std::string &modelPath, int n_ctx, int ngl) override;
|
||||
size_t stateSize() const override;
|
||||
size_t saveState(std::span<uint8_t> stateOut, std::vector<Token> &inputTokensOut) const override;
|
||||
size_t restoreState(std::span<const uint8_t> state, std::span<const Token> inputTokens) override;
|
||||
size_t saveState(uint8_t *dest) const override;
|
||||
size_t restoreState(const uint8_t *src) override;
|
||||
void setThreadCount(int32_t n_threads) override;
|
||||
int32_t threadCount() const override;
|
||||
std::vector<GPUDevice> availableGPUDevices(size_t memoryRequired = 0) const override;
|
||||
@@ -49,36 +47,27 @@ public:
|
||||
void embed(const std::vector<std::string> &texts, float *embeddings, bool isRetrieval, int dimensionality = -1,
|
||||
size_t *tokenCount = nullptr, bool doMean = true, bool atlas = false) override;
|
||||
|
||||
int32_t contextLength() const override;
|
||||
auto specialTokens() -> std::unordered_map<std::string, std::string> const override;
|
||||
|
||||
protected:
|
||||
std::vector<Token> tokenize(std::string_view str) const override;
|
||||
bool isSpecialToken(Token id) const override;
|
||||
std::string tokenToString(Token id) const override;
|
||||
void initSampler(const PromptContext &ctx) override;
|
||||
Token sampleToken() const override;
|
||||
bool evalTokens(int32_t nPast, std::span<const Token> tokens) const override;
|
||||
void shiftContext(const PromptContext &promptCtx, int32_t *nPast) override;
|
||||
int32_t inputLength() const override;
|
||||
int32_t computeModelInputPosition(std::span<const Token> input) const override;
|
||||
void setModelInputPosition(int32_t pos) override;
|
||||
void appendInputToken(Token tok) override;
|
||||
std::span<const Token> inputTokens() const override;
|
||||
const std::vector<Token> &endTokens() const override;
|
||||
bool shouldAddBOS() const override;
|
||||
int32_t maxContextLength(std::string const &modelPath) const override;
|
||||
int32_t layerCount(std::string const &modelPath) const override;
|
||||
auto chatTemplate(const char *modelPath) const -> std::expected<std::string, std::string> override;
|
||||
|
||||
void embedInternal(const std::vector<std::string> &texts, float *embeddings, std::string prefix, int dimensionality,
|
||||
size_t *tokenCount, bool doMean, bool atlas, EmbedCancelCallback *cancelCb,
|
||||
const EmbModelSpec *spec);
|
||||
|
||||
private:
|
||||
std::unique_ptr<LLamaPrivate> d_ptr;
|
||||
bool m_supportsEmbedding = false;
|
||||
bool m_supportsCompletion = false;
|
||||
|
||||
protected:
|
||||
std::vector<Token> tokenize(PromptContext &ctx, std::string_view str, bool special) override;
|
||||
bool isSpecialToken(Token id) const override;
|
||||
std::string tokenToString(Token id) const override;
|
||||
Token sampleToken(PromptContext &ctx) const override;
|
||||
bool evalTokens(PromptContext &ctx, const std::vector<int32_t> &tokens) const override;
|
||||
void shiftContext(PromptContext &promptCtx) override;
|
||||
int32_t contextLength() const override;
|
||||
const std::vector<Token> &endTokens() const override;
|
||||
bool shouldAddBOS() const override;
|
||||
int32_t maxContextLength(std::string const &modelPath) const override;
|
||||
int32_t layerCount(std::string const &modelPath) const override;
|
||||
|
||||
void embedInternal(const std::vector<std::string> &texts, float *embeddings, std::string prefix, int dimensionality,
|
||||
size_t *tokenCount, bool doMean, bool atlas, EmbedCancelCallback *cancelCb,
|
||||
const EmbModelSpec *spec);
|
||||
};
|
||||
|
||||
#endif // LLAMAMODEL_H
|
||||
|
||||
@@ -326,12 +326,6 @@ bool LLModel::Implementation::isEmbeddingModel(const std::string &modelPath)
|
||||
return llama && llama->isEmbeddingModel(modelPath);
|
||||
}
|
||||
|
||||
auto LLModel::Implementation::chatTemplate(const char *modelPath) -> std::expected<std::string, std::string>
|
||||
{
|
||||
auto *llama = constructGlobalLlama();
|
||||
return llama ? llama->chatTemplate(modelPath) : std::unexpected("backend not available");
|
||||
}
|
||||
|
||||
void LLModel::Implementation::setImplementationsSearchPath(const std::string& path)
|
||||
{
|
||||
s_implementations_search_path = path;
|
||||
|
||||
@@ -7,20 +7,17 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <span>
|
||||
|
||||
namespace ranges = std::ranges;
|
||||
|
||||
static_assert(sizeof(token_t) == sizeof(LLModel::Token));
|
||||
|
||||
struct LLModelWrapper {
|
||||
LLModel *llModel = nullptr;
|
||||
LLModel::PromptContext promptContext;
|
||||
~LLModelWrapper() { delete llModel; }
|
||||
};
|
||||
|
||||
@@ -88,80 +85,74 @@ bool llmodel_isModelLoaded(llmodel_model model)
|
||||
return wrapper->llModel->isModelLoaded();
|
||||
}
|
||||
|
||||
uint64_t llmodel_state_get_size(llmodel_model model)
|
||||
uint64_t llmodel_get_state_size(llmodel_model model)
|
||||
{
|
||||
auto *wrapper = static_cast<LLModelWrapper *>(model);
|
||||
return wrapper->llModel->stateSize();
|
||||
}
|
||||
|
||||
uint64_t llmodel_state_get_data(llmodel_model model, uint8_t *state_out, uint64_t state_size,
|
||||
token_t **input_tokens_out, uint64_t *n_input_tokens)
|
||||
uint64_t llmodel_save_state_data(llmodel_model model, uint8_t *dest)
|
||||
{
|
||||
auto *wrapper = static_cast<LLModelWrapper *>(model);
|
||||
std::vector<LLModel::Token> inputTokens;
|
||||
auto bytesWritten = wrapper->llModel->saveState({state_out, size_t(state_size)}, inputTokens);
|
||||
if (bytesWritten) {
|
||||
auto *buf = new LLModel::Token[inputTokens.size()];
|
||||
ranges::copy(inputTokens, buf);
|
||||
*input_tokens_out = buf;
|
||||
*n_input_tokens = uint64_t(inputTokens.size());
|
||||
} else {
|
||||
*input_tokens_out = nullptr;
|
||||
*n_input_tokens = 0;
|
||||
}
|
||||
return bytesWritten;
|
||||
return wrapper->llModel->saveState(dest);
|
||||
}
|
||||
|
||||
void llmodel_state_free_input_tokens(LLModel::Token *input_tokens)
|
||||
{
|
||||
delete[] input_tokens;
|
||||
}
|
||||
|
||||
uint64_t llmodel_state_set_data(llmodel_model model, const uint8_t *state, uint64_t state_size,
|
||||
const token_t *input_tokens, uint64_t n_input_tokens)
|
||||
uint64_t llmodel_restore_state_data(llmodel_model model, const uint8_t *src)
|
||||
{
|
||||
auto *wrapper = static_cast<LLModelWrapper *>(model);
|
||||
return wrapper->llModel->restoreState({state, size_t(state_size)}, {input_tokens, size_t(n_input_tokens)});
|
||||
return wrapper->llModel->restoreState(src);
|
||||
}
|
||||
|
||||
bool llmodel_prompt(llmodel_model model,
|
||||
const char *prompt,
|
||||
llmodel_prompt_callback prompt_callback,
|
||||
llmodel_response_callback response_callback,
|
||||
llmodel_prompt_context *ctx,
|
||||
const char **error)
|
||||
void llmodel_prompt(llmodel_model model, const char *prompt,
|
||||
const char *prompt_template,
|
||||
llmodel_prompt_callback prompt_callback,
|
||||
llmodel_response_callback response_callback,
|
||||
bool allow_context_shift,
|
||||
llmodel_prompt_context *ctx,
|
||||
bool special,
|
||||
const char *fake_reply)
|
||||
{
|
||||
auto *wrapper = static_cast<LLModelWrapper *>(model);
|
||||
|
||||
auto response_func = [response_callback](int32_t token_id, const std::string &response) {
|
||||
return response_callback(token_id, response.c_str());
|
||||
};
|
||||
|
||||
// Copy the C prompt context
|
||||
LLModel::PromptContext promptContext {
|
||||
.n_predict = ctx->n_predict,
|
||||
.top_k = ctx->top_k,
|
||||
.top_p = ctx->top_p,
|
||||
.min_p = ctx->min_p,
|
||||
.temp = ctx->temp,
|
||||
.n_batch = ctx->n_batch,
|
||||
.repeat_penalty = ctx->repeat_penalty,
|
||||
.repeat_last_n = ctx->repeat_last_n,
|
||||
.contextErase = ctx->context_erase,
|
||||
};
|
||||
|
||||
auto prompt_func = [prompt_callback](std::span<const LLModel::Token> token_ids, bool cached) {
|
||||
return prompt_callback(token_ids.data(), token_ids.size(), cached);
|
||||
};
|
||||
auto response_func = [response_callback](LLModel::Token token_id, std::string_view piece) {
|
||||
return response_callback(token_id, piece.data());
|
||||
};
|
||||
wrapper->promptContext.n_past = ctx->n_past;
|
||||
wrapper->promptContext.n_ctx = ctx->n_ctx;
|
||||
wrapper->promptContext.n_predict = ctx->n_predict;
|
||||
wrapper->promptContext.top_k = ctx->top_k;
|
||||
wrapper->promptContext.top_p = ctx->top_p;
|
||||
wrapper->promptContext.min_p = ctx->min_p;
|
||||
wrapper->promptContext.temp = ctx->temp;
|
||||
wrapper->promptContext.n_batch = ctx->n_batch;
|
||||
wrapper->promptContext.repeat_penalty = ctx->repeat_penalty;
|
||||
wrapper->promptContext.repeat_last_n = ctx->repeat_last_n;
|
||||
wrapper->promptContext.contextErase = ctx->context_erase;
|
||||
|
||||
// Call the C++ prompt method
|
||||
try {
|
||||
wrapper->llModel->prompt(prompt, prompt_func, response_func, promptContext);
|
||||
} catch (std::exception const &e) {
|
||||
llmodel_set_error(error, e.what());
|
||||
return false;
|
||||
}
|
||||
wrapper->llModel->prompt(prompt, prompt_template, prompt_callback, response_func, allow_context_shift,
|
||||
wrapper->promptContext, special,
|
||||
fake_reply ? std::make_optional<std::string_view>(fake_reply) : std::nullopt);
|
||||
|
||||
return true;
|
||||
// Update the C context by giving access to the wrappers raw pointers to std::vector data
|
||||
// which involves no copies
|
||||
ctx->tokens = wrapper->promptContext.tokens.data();
|
||||
ctx->tokens_size = wrapper->promptContext.tokens.size();
|
||||
|
||||
// Update the rest of the C prompt context
|
||||
ctx->n_past = wrapper->promptContext.n_past;
|
||||
ctx->n_ctx = wrapper->promptContext.n_ctx;
|
||||
ctx->n_predict = wrapper->promptContext.n_predict;
|
||||
ctx->top_k = wrapper->promptContext.top_k;
|
||||
ctx->top_p = wrapper->promptContext.top_p;
|
||||
ctx->min_p = wrapper->promptContext.min_p;
|
||||
ctx->temp = wrapper->promptContext.temp;
|
||||
ctx->n_batch = wrapper->promptContext.n_batch;
|
||||
ctx->repeat_penalty = wrapper->promptContext.repeat_penalty;
|
||||
ctx->repeat_last_n = wrapper->promptContext.repeat_last_n;
|
||||
ctx->context_erase = wrapper->promptContext.contextErase;
|
||||
}
|
||||
|
||||
float *llmodel_embed(
|
||||
@@ -300,21 +291,3 @@ const char *llmodel_model_gpu_device_name(llmodel_model model)
|
||||
const auto *wrapper = static_cast<LLModelWrapper *>(model);
|
||||
return wrapper->llModel->gpuDeviceName();
|
||||
}
|
||||
|
||||
int32_t llmodel_count_prompt_tokens(llmodel_model model, const char *prompt, const char **error)
|
||||
{
|
||||
auto *wrapper = static_cast<const LLModelWrapper *>(model);
|
||||
try {
|
||||
return wrapper->llModel->countPromptTokens(prompt);
|
||||
} catch (const std::exception& e) {
|
||||
llmodel_set_error(error, e.what());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void llmodel_model_foreach_special_token(llmodel_model model, llmodel_special_token_callback callback)
|
||||
{
|
||||
auto *wrapper = static_cast<const LLModelWrapper *>(model);
|
||||
for (auto &[name, token] : wrapper->llModel->specialTokens())
|
||||
callback(name.c_str(), token.c_str());
|
||||
}
|
||||
|
||||
@@ -4,120 +4,209 @@
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace ranges = std::ranges;
|
||||
namespace views = std::ranges::views;
|
||||
|
||||
void LLModel::prompt(
|
||||
std::string_view prompt,
|
||||
const PromptCallback &promptCallback,
|
||||
const ResponseCallback &responseCallback,
|
||||
const PromptContext &promptCtx
|
||||
) {
|
||||
if (!isModelLoaded())
|
||||
throw std::invalid_argument("Attempted to prompt an unloaded model.");
|
||||
if (!supportsCompletion())
|
||||
throw std::invalid_argument("Not a text completion model.");
|
||||
if (!promptCtx.n_batch)
|
||||
throw std::invalid_argument("Batch size cannot be zero.");
|
||||
if (!promptCtx.n_predict)
|
||||
return; // nothing requested
|
||||
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 embd_inp = tokenize(prompt);
|
||||
if (embd_inp.empty())
|
||||
throw std::invalid_argument("Prompt tokenized to zero tokens.");
|
||||
auto it = std::sregex_iterator(tmpl.begin(), tmpl.end(), placeholderRegex);
|
||||
placeholders.clear();
|
||||
placeholders.insert(placeholders.end(), it, std::sregex_iterator());
|
||||
|
||||
if (auto res = decodePrompt(promptCallback, promptCtx, std::move(embd_inp)))
|
||||
generateResponse(responseCallback, promptCtx, /*n_past*/ *res);
|
||||
if (placeholders.size() > 2) {
|
||||
err = "ERROR: expected at most two placeholders, got " + std::to_string(placeholders.size());
|
||||
return false;
|
||||
}
|
||||
if (placeholders.size() >= 1 && placeholders[0].str() != "%1") {
|
||||
err = "ERROR: first placeholder must be %1, got " + placeholders[0].str();
|
||||
return false;
|
||||
}
|
||||
if (placeholders.size() >= 2 && placeholders[1].str() != "%2") {
|
||||
err = "ERROR: second placeholder must be %2, got " + placeholders[1].str();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t LLModel::countPromptTokens(std::string_view prompt) const
|
||||
void LLModel::prompt(const std::string &prompt,
|
||||
const std::string &promptTemplate,
|
||||
std::function<bool(int32_t)> promptCallback,
|
||||
std::function<bool(int32_t, const std::string&)> responseCallback,
|
||||
bool allowContextShift,
|
||||
PromptContext &promptCtx,
|
||||
bool special,
|
||||
std::optional<std::string_view> fakeReply)
|
||||
{
|
||||
if (!isModelLoaded())
|
||||
throw std::invalid_argument("Attempted to tokenize with an unloaded model.");
|
||||
return int32_t(tokenize(prompt).size());
|
||||
}
|
||||
|
||||
auto LLModel::decodePrompt(
|
||||
const PromptCallback &promptCallback,
|
||||
const PromptContext &promptCtx,
|
||||
std::vector<Token> embd_inp
|
||||
) -> std::optional<int32_t>
|
||||
{
|
||||
assert(!embd_inp.empty());
|
||||
|
||||
int32_t nCtx = contextLength();
|
||||
int32_t n_batch = std::min(promptCtx.n_batch, LLMODEL_MAX_PROMPT_BATCH);
|
||||
|
||||
// Find the greatest n_past where the beginning of embd_inp matches the end of the token cache, starting at the
|
||||
// requested n_past.
|
||||
// This is used to skip unnecessary work when the prompt shares a common prefix with the previous result.
|
||||
int32_t nPast = computeModelInputPosition(embd_inp);
|
||||
|
||||
// always decode up to a full batch before generating, even if cached
|
||||
nPast -= std::min(n_batch, nPast);
|
||||
|
||||
// TODO(jared): generalize this to find the smallest new_embd_inp.size() - nPast given the cache
|
||||
if (!nPast && int32_t(embd_inp.size()) > nCtx) {
|
||||
// no cache hit -> shift the input before even processing
|
||||
|
||||
int32_t nKeep = shouldAddBOS();
|
||||
auto newLength = int32_t(nCtx * (1.f - promptCtx.contextErase));
|
||||
int32_t nDiscard = int32_t(embd_inp.size()) - std::max(1, std::min(nCtx, newLength));
|
||||
|
||||
// execute the callback even for skipped tokens. this misrepresents the position of BOS but we don't care
|
||||
auto discardedTokens = embd_inp | views::drop(nKeep) | views::take(nDiscard);
|
||||
if (!promptCallback(discardedTokens, true))
|
||||
return std::nullopt;
|
||||
|
||||
// erase nDiscard tokens
|
||||
embd_inp.erase(discardedTokens.begin(), discardedTokens.end());
|
||||
assert(int32_t(embd_inp.size()) <= nCtx);
|
||||
|
||||
// check the cache again, just in case
|
||||
nPast = computeModelInputPosition(embd_inp);
|
||||
nPast -= std::min(n_batch, nPast);
|
||||
if (!isModelLoaded()) {
|
||||
std::cerr << implementation().modelType() << " ERROR: prompt won't work with an unloaded model!\n";
|
||||
return;
|
||||
}
|
||||
|
||||
setModelInputPosition(nPast);
|
||||
if (!supportsCompletion()) {
|
||||
std::string errorMessage = "ERROR: this model does not support text completion or chat!";
|
||||
responseCallback(-1, errorMessage);
|
||||
std::cerr << implementation().modelType() << " " << errorMessage << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// execute the callback even for skipped tokens
|
||||
if (!promptCallback(embd_inp | views::take(nPast), true))
|
||||
return std::nullopt;
|
||||
// sanity checks
|
||||
if (promptCtx.n_past > contextLength()) {
|
||||
std::ostringstream ss;
|
||||
ss << "n_past=" << promptCtx.n_past << " is past end of context length=" << contextLength();
|
||||
throw std::out_of_range(ss.str());
|
||||
}
|
||||
if (promptCtx.n_past > promptCtx.tokens.size()) {
|
||||
std::ostringstream ss;
|
||||
ss << "n_past=" << promptCtx.n_past << " is past end of token cache length=" << promptCtx.tokens.size();
|
||||
throw std::out_of_range(ss.str());
|
||||
}
|
||||
|
||||
// process the prompt in batches
|
||||
for (int32_t i = nPast; i < embd_inp.size();) {
|
||||
auto batch_end = std::min(i + n_batch, int32_t(embd_inp.size()));
|
||||
std::span batch(embd_inp.begin() + i, embd_inp.begin() + batch_end);
|
||||
promptCtx.n_ctx = contextLength();
|
||||
promptCtx.n_batch = std::min(promptCtx.n_batch, LLMODEL_MAX_PROMPT_BATCH);
|
||||
|
||||
// Check if the context has run out...
|
||||
if (nPast + int32_t(batch.size()) > nCtx) {
|
||||
shiftContext(promptCtx, &nPast);
|
||||
assert(nPast + int32_t(batch.size()) <= nCtx);
|
||||
if (promptCtx.n_past < promptCtx.tokens.size())
|
||||
promptCtx.tokens.resize(promptCtx.n_past);
|
||||
m_tokenize_last_token = promptCtx.tokens.empty() ? -1 : promptCtx.tokens.back(); // not serialized
|
||||
|
||||
// parse the prompt template
|
||||
std::vector<std::smatch> placeholders;
|
||||
{
|
||||
std::string err;
|
||||
if (!parsePromptTemplate(promptTemplate, placeholders, err)) {
|
||||
responseCallback(-1, err);
|
||||
std::cerr << err << "\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto old_n_past = promptCtx.n_past; // prepare to fake n_past for tokenize
|
||||
|
||||
// tokenize the user prompt
|
||||
std::vector<Token> embd_inp;
|
||||
if (placeholders.empty()) {
|
||||
// this is unusual, but well-defined
|
||||
std::cerr << __func__ << ": prompt template has no placeholder\n";
|
||||
embd_inp = tokenize(promptCtx, promptTemplate, true);
|
||||
} else {
|
||||
// template: beginning of user prompt
|
||||
const auto &phUser = placeholders[0];
|
||||
std::string userPrefix(phUser.prefix());
|
||||
if (!userPrefix.empty()) {
|
||||
embd_inp = tokenize(promptCtx, userPrefix, true);
|
||||
promptCtx.n_past += embd_inp.size();
|
||||
}
|
||||
|
||||
// FIXME(Adam): We should find a way to bubble these strings to the UI level to allow for translation
|
||||
if (!evalTokens(nPast, batch))
|
||||
throw std::runtime_error("An internal error was encountered during prompt processing.");
|
||||
// user input (shouldn't have special token processing)
|
||||
auto tokens = tokenize(promptCtx, prompt, special);
|
||||
embd_inp.insert(embd_inp.end(), tokens.begin(), tokens.end());
|
||||
promptCtx.n_past += tokens.size();
|
||||
|
||||
for (auto &tok : batch) {
|
||||
appendInputToken(tok);
|
||||
nPast++;
|
||||
if (!promptCallback({ &tok, 1 }, false))
|
||||
return std::nullopt;
|
||||
// template: end of user prompt + start of assistant prompt
|
||||
size_t start = phUser.position() + phUser.length();
|
||||
size_t end = placeholders.size() >= 2 ? placeholders[1].position() : promptTemplate.length();
|
||||
auto userToAsst = promptTemplate.substr(start, end - start);
|
||||
if (!userToAsst.empty()) {
|
||||
tokens = tokenize(promptCtx, userToAsst, true);
|
||||
embd_inp.insert(embd_inp.end(), tokens.begin(), tokens.end());
|
||||
promptCtx.n_past += tokens.size();
|
||||
}
|
||||
}
|
||||
|
||||
promptCtx.n_past = old_n_past; // restore n_past so decodePrompt can increment it
|
||||
|
||||
// decode the user prompt
|
||||
if (!decodePrompt(promptCallback, responseCallback, allowContextShift, promptCtx, embd_inp))
|
||||
return; // error
|
||||
|
||||
// decode the assistant's reply, either generated or spoofed
|
||||
if (!fakeReply) {
|
||||
generateResponse(responseCallback, allowContextShift, promptCtx);
|
||||
} else {
|
||||
embd_inp = tokenize(promptCtx, *fakeReply, false);
|
||||
if (!decodePrompt(promptCallback, responseCallback, allowContextShift, promptCtx, embd_inp, true))
|
||||
return; // error
|
||||
}
|
||||
|
||||
// decode the rest of the prompt template
|
||||
// template: end of assistant prompt
|
||||
std::string asstSuffix;
|
||||
if (placeholders.size() >= 2) {
|
||||
size_t start = placeholders[1].position() + placeholders[1].length();
|
||||
asstSuffix = promptTemplate.substr(start);
|
||||
} else {
|
||||
asstSuffix = "\n\n"; // default to a blank link, good for e.g. Alpaca
|
||||
}
|
||||
if (!asstSuffix.empty()) {
|
||||
embd_inp = tokenize(promptCtx, asstSuffix, true);
|
||||
decodePrompt(promptCallback, responseCallback, allowContextShift, promptCtx, embd_inp);
|
||||
}
|
||||
}
|
||||
|
||||
// returns false on error
|
||||
bool LLModel::decodePrompt(std::function<bool(int32_t)> promptCallback,
|
||||
std::function<bool(int32_t, const std::string&)> responseCallback,
|
||||
bool allowContextShift,
|
||||
PromptContext &promptCtx,
|
||||
std::vector<Token> embd_inp,
|
||||
bool isResponse) {
|
||||
if ((int) embd_inp.size() > promptCtx.n_ctx - 4) {
|
||||
responseCallback(-1, "ERROR: The prompt size exceeds the context window size and cannot be processed.");
|
||||
std::cerr << implementation().modelType() << " ERROR: The prompt is " << embd_inp.size() <<
|
||||
" tokens and the context window is " << promptCtx.n_ctx << "!\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME(jared): There are mitigations for this situation, such as making room before
|
||||
// copying the prompt context, or restoring the KV cache when we restore the prompt
|
||||
// context.
|
||||
if (!allowContextShift && promptCtx.n_past + embd_inp.size() > promptCtx.n_ctx) {
|
||||
std::cerr << "LLModel Warning: Not enough space, n_past=" << promptCtx.n_past << ", n_eval=" << embd_inp.size()
|
||||
<< ", n_ctx=" << promptCtx.n_ctx << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// process the prompt in batches
|
||||
size_t i = 0;
|
||||
while (i < embd_inp.size()) {
|
||||
size_t batch_end = std::min(i + promptCtx.n_batch, embd_inp.size());
|
||||
std::vector<Token> batch(embd_inp.begin() + i, embd_inp.begin() + batch_end);
|
||||
|
||||
// Check if the context has run out...
|
||||
if (promptCtx.n_past + int32_t(batch.size()) > promptCtx.n_ctx) {
|
||||
assert(allowContextShift);
|
||||
shiftContext(promptCtx);
|
||||
assert(promptCtx.n_past + int32_t(batch.size()) <= promptCtx.n_ctx);
|
||||
}
|
||||
|
||||
if (!evalTokens(promptCtx, batch)) {
|
||||
std::cerr << implementation().modelType() << " ERROR: Failed to process prompt\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t tokens = batch_end - i;
|
||||
for (size_t t = 0; t < tokens; ++t) {
|
||||
promptCtx.tokens.push_back(batch.at(t));
|
||||
promptCtx.n_past += 1;
|
||||
Token tok = batch.at(t);
|
||||
bool res = isResponse ? responseCallback(tok, tokenToString(tok)) : promptCallback(tok);
|
||||
if (!res)
|
||||
return false;
|
||||
}
|
||||
i = batch_end;
|
||||
}
|
||||
|
||||
return nPast;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -139,17 +228,21 @@ static std::string::size_type stringsOverlap(const std::string &s, const std::st
|
||||
return std::string::npos;
|
||||
}
|
||||
|
||||
void LLModel::generateResponse(
|
||||
const ResponseCallback &responseCallback,
|
||||
const PromptContext &promptCtx,
|
||||
int32_t nPast
|
||||
) {
|
||||
void LLModel::generateResponse(std::function<bool(int32_t, const std::string&)> responseCallback,
|
||||
bool allowContextShift,
|
||||
PromptContext &promptCtx) {
|
||||
static const char *stopSequences[] {
|
||||
"### System", "### Instruction", "### Human", "### User", "### Response", "### Assistant", "### Context",
|
||||
"<|im_start|>", "<|im_end|>", "<|endoftext|>",
|
||||
"### Instruction", "### Prompt", "### Response", "### Human", "### Assistant", "### Context",
|
||||
};
|
||||
|
||||
initSampler(promptCtx);
|
||||
// Don't even start if there is no room
|
||||
if (!promptCtx.n_predict)
|
||||
return;
|
||||
if (!allowContextShift && promptCtx.n_past >= promptCtx.n_ctx) {
|
||||
std::cerr << "LLModel Warning: Not enough space, n_past=" << promptCtx.n_past << ", n_ctx=" << promptCtx.n_ctx
|
||||
<< "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::string cachedResponse;
|
||||
std::vector<Token> cachedTokens;
|
||||
@@ -158,25 +251,31 @@ void LLModel::generateResponse(
|
||||
// Predict next tokens
|
||||
for (bool stop = false; !stop;) {
|
||||
// Sample next token
|
||||
std::optional<Token> new_tok = sampleToken();
|
||||
std::optional<Token> new_tok = sampleToken(promptCtx);
|
||||
std::string new_piece = tokenToString(new_tok.value());
|
||||
cachedTokens.push_back(new_tok.value());
|
||||
cachedResponse += new_piece;
|
||||
|
||||
auto accept = [this, &promptCtx, &new_tok, &nPast] {
|
||||
auto accept = [this, &promptCtx, &cachedTokens, &new_tok, allowContextShift]() -> bool {
|
||||
// Shift context if out of space
|
||||
if (nPast >= contextLength()) {
|
||||
shiftContext(promptCtx, &nPast);
|
||||
assert(nPast < contextLength());
|
||||
if (promptCtx.n_past >= promptCtx.n_ctx) {
|
||||
(void)allowContextShift;
|
||||
assert(allowContextShift);
|
||||
shiftContext(promptCtx);
|
||||
assert(promptCtx.n_past < promptCtx.n_ctx);
|
||||
}
|
||||
|
||||
// Accept the token
|
||||
Token tok = std::exchange(new_tok, std::nullopt).value();
|
||||
if (!evalTokens(nPast, { &tok, 1 }))
|
||||
throw std::runtime_error("An internal error was encountered during response generation.");
|
||||
if (!evalTokens(promptCtx, { tok })) {
|
||||
// TODO(jared): raise an exception
|
||||
std::cerr << implementation().modelType() << " ERROR: Failed to predict next token\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
appendInputToken(tok);
|
||||
nPast++;
|
||||
promptCtx.tokens.push_back(tok);
|
||||
promptCtx.n_past += 1;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Check for EOS
|
||||
@@ -213,6 +312,13 @@ void LLModel::generateResponse(
|
||||
lengthLimit = cachedResponse.size() - new_piece.size();
|
||||
}
|
||||
|
||||
// Optionally stop if the context will run out
|
||||
if (!allowContextShift && promptCtx.n_past + cachedTokens.size() >= promptCtx.n_ctx) {
|
||||
std::cerr << "LLModel Warning: Not enough space, n_past=" << promptCtx.n_past << ", n_ctx="
|
||||
<< promptCtx.n_ctx << "\n";
|
||||
stop = true;
|
||||
}
|
||||
|
||||
// Empty the cache, up to the length limit
|
||||
std::string::size_type responseLength = 0;
|
||||
while (!cachedTokens.empty()) {
|
||||
@@ -229,8 +335,8 @@ void LLModel::generateResponse(
|
||||
cachedResponse.erase(cachedResponse.begin(), cachedResponse.begin() + piece.size());
|
||||
|
||||
// Accept the token, if needed (not cached)
|
||||
if (cachedTokens.empty() && new_tok)
|
||||
accept();
|
||||
if (cachedTokens.empty() && new_tok && !accept())
|
||||
return;
|
||||
|
||||
// Send the token
|
||||
if (!responseCallback(tok, piece) || ++n_predicted >= promptCtx.n_predict) {
|
||||
@@ -249,23 +355,24 @@ void LLModel::generateResponse(
|
||||
assert(!cachedTokens.empty() && cachedTokens.back() == new_tok);
|
||||
if (stop) {
|
||||
cachedTokens.pop_back();
|
||||
} else {
|
||||
accept();
|
||||
} else if (!accept()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inputLength() < cachedTokens.size()) {
|
||||
auto &tokens = promptCtx.tokens;
|
||||
if (tokens.size() < cachedTokens.size()) {
|
||||
/* This is theoretically possible if the longest stop sequence is greater than
|
||||
* n_ctx * contextErase tokens. */
|
||||
throw std::runtime_error("shifted too much context, can't go back");
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
auto inp = inputTokens();
|
||||
auto discard_start = inp.end() - cachedTokens.size();
|
||||
assert(std::equal(discard_start, inp.end(), cachedTokens.begin()));
|
||||
#endif
|
||||
auto discard_start = tokens.end() - cachedTokens.size();
|
||||
assert(std::equal(discard_start, tokens.end(), cachedTokens.begin()));
|
||||
tokens.erase(discard_start, tokens.end());
|
||||
|
||||
promptCtx.n_past -= cachedTokens.size();
|
||||
}
|
||||
|
||||
void LLModel::embed(
|
||||
|
||||
49
gpt4all-backend/src/llmodel_shared.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <ggml.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
struct llm_buffer {
|
||||
uint8_t * addr = NULL;
|
||||
size_t size = 0;
|
||||
|
||||
void resize(size_t size) {
|
||||
delete[] addr;
|
||||
addr = new uint8_t[size];
|
||||
this->size = size;
|
||||
}
|
||||
|
||||
~llm_buffer() {
|
||||
delete[] addr;
|
||||
}
|
||||
};
|
||||
|
||||
struct llm_kv_cache {
|
||||
struct ggml_tensor * k;
|
||||
struct ggml_tensor * v;
|
||||
|
||||
struct ggml_context * ctx = NULL;
|
||||
|
||||
llm_buffer buf;
|
||||
|
||||
int n; // number of tokens currently in the cache
|
||||
|
||||
~llm_kv_cache() {
|
||||
if (ctx) {
|
||||
ggml_free(ctx);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
plan.work_data = buf.addr;
|
||||
}
|
||||
ggml_graph_compute(graph, &plan);
|
||||
}
|
||||
339
gpt4all-backend/src/utils.cpp
Normal file
@@ -0,0 +1,339 @@
|
||||
#include "utils.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <regex>
|
||||
#include <utility>
|
||||
|
||||
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);
|
||||
pos += replacement.length();
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, int32_t> json_parse(const std::string & fname)
|
||||
{
|
||||
std::map<std::string, int32_t> result;
|
||||
|
||||
// read file into string
|
||||
std::string json;
|
||||
{
|
||||
std::ifstream ifs(fname);
|
||||
if (!ifs) {
|
||||
fprintf(stderr, "Failed to open %s\n", fname.c_str());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
json = std::string((std::istreambuf_iterator<char>(ifs)),
|
||||
(std::istreambuf_iterator<char>()));
|
||||
}
|
||||
|
||||
if (json[0] != '{') {
|
||||
return result;
|
||||
}
|
||||
|
||||
// parse json
|
||||
{
|
||||
bool has_key = false;
|
||||
bool in_token = false;
|
||||
|
||||
std::string str_key = "";
|
||||
std::string str_val = "";
|
||||
|
||||
int n = json.size();
|
||||
for (int i = 1; i < n; ++i) {
|
||||
if (!in_token) {
|
||||
if (json[i] == ' ') continue;
|
||||
if (json[i] == '"') {
|
||||
in_token = true;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (json[i] == '\\' && i+1 < n) {
|
||||
if (has_key == false) {
|
||||
str_key += json[i];
|
||||
} else {
|
||||
str_val += json[i];
|
||||
}
|
||||
++i;
|
||||
} else if (json[i] == '"') {
|
||||
if (has_key == false) {
|
||||
has_key = true;
|
||||
++i;
|
||||
while (json[i] == ' ') ++i;
|
||||
++i; // :
|
||||
while (json[i] == ' ') ++i;
|
||||
if (json[i] != '\"') {
|
||||
while (json[i] != ',' && json[i] != '}') {
|
||||
str_val += json[i++];
|
||||
}
|
||||
has_key = false;
|
||||
} else {
|
||||
in_token = true;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
has_key = false;
|
||||
}
|
||||
|
||||
::replace(str_key, "\\u0120", " " ); // \u0120 -> space
|
||||
::replace(str_key, "\\u010a", "\n"); // \u010a -> new line
|
||||
::replace(str_key, "\\\"", "\""); // \\\" -> "
|
||||
|
||||
try {
|
||||
result[str_key] = std::stoi(str_val);
|
||||
} catch (...) {
|
||||
//fprintf(stderr, "%s: ignoring key '%s' with value '%s'\n", fname.c_str(), str_key.c_str(), str_val.c_str());
|
||||
|
||||
}
|
||||
str_key = "";
|
||||
str_val = "";
|
||||
in_token = false;
|
||||
continue;
|
||||
}
|
||||
if (has_key == false) {
|
||||
str_key += json[i];
|
||||
} else {
|
||||
str_val += json[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
std::string str = text;
|
||||
std::string pat = R"('s|'t|'re|'ve|'m|'ll|'d| ?[[:alpha:]]+| ?[[:digit:]]+| ?[^\s[:alpha:][:digit:]]+|\s+(?!\S)|\s+)";
|
||||
|
||||
std::regex re(pat);
|
||||
std::smatch m;
|
||||
|
||||
while (std::regex_search(str, m, re)) {
|
||||
for (auto x : m) {
|
||||
words.push_back(x);
|
||||
}
|
||||
str = m.suffix();
|
||||
}
|
||||
}
|
||||
|
||||
// find the longest tokens that form the words:
|
||||
std::vector<gpt_vocab::id> tokens;
|
||||
for (const auto & word : words) {
|
||||
if (word.size() == 0) continue;
|
||||
|
||||
int i = 0;
|
||||
int n = word.size();
|
||||
while (i < n) {
|
||||
int j = n;
|
||||
while (j > i) {
|
||||
auto it = vocab.token_to_id.find(word.substr(i, j-i));
|
||||
if (it != vocab.token_to_id.end()) {
|
||||
tokens.push_back(it->second);
|
||||
i = j;
|
||||
break;
|
||||
}
|
||||
--j;
|
||||
}
|
||||
if (i == n) {
|
||||
break;
|
||||
}
|
||||
if (j == i) {
|
||||
auto sub = word.substr(i, 1);
|
||||
if (vocab.token_to_id.find(sub) != vocab.token_to_id.end()) {
|
||||
tokens.push_back(vocab.token_to_id.at(sub));
|
||||
} else {
|
||||
fprintf(stderr, "%s: unknown token '%s'\n", __func__, sub.data());
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// Generate the subpattern from the special_tokens vector if it's not empty
|
||||
if (!vocab.special_tokens.empty()) {
|
||||
std::vector<gpt_vocab::id> out;
|
||||
std::vector<std::string> chunks;
|
||||
std::string str = text;
|
||||
std::string special_tokens_subpattern;
|
||||
for (const auto &token : vocab.special_tokens) {
|
||||
if (!special_tokens_subpattern.empty()) {
|
||||
special_tokens_subpattern += "|";
|
||||
}
|
||||
special_tokens_subpattern += regex_escape(token);
|
||||
}
|
||||
std::regex re(special_tokens_subpattern);
|
||||
std::smatch m;
|
||||
while (std::regex_search(str, m, re)) {
|
||||
auto tok = vocab.token_to_id.find(m.str());
|
||||
if (tok != vocab.token_to_id.end()) {
|
||||
auto tokid = tok->second;
|
||||
auto pfxtoks = gpt_tokenize_inner(vocab, m.prefix());
|
||||
out.insert(out.end(), pfxtoks.begin(), pfxtoks.end());
|
||||
out.push_back(tokid);
|
||||
str = m.suffix();
|
||||
}
|
||||
}
|
||||
if (!str.empty()) {
|
||||
auto tokrest = gpt_tokenize_inner(vocab, str);
|
||||
out.insert(out.end(), tokrest.begin(), tokrest.end());
|
||||
}
|
||||
return out;
|
||||
} else {
|
||||
return gpt_tokenize_inner(vocab, text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
for (const auto & kv : vocab.token_to_id) {
|
||||
vocab.id_to_token[kv.second] = kv.first;
|
||||
}
|
||||
|
||||
printf("%s: vocab size = %d\n", __func__, (int) vocab.token_to_id.size());
|
||||
|
||||
// print the vocabulary
|
||||
//for (auto kv : vocab.token_to_id) {
|
||||
// printf("'%s' -> %d\n", kv.first.data(), kv.second);
|
||||
//}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
gpt_vocab::id gpt_sample_top_k_top_p(
|
||||
const size_t actualVocabSize,
|
||||
const int32_t * last_n_tokens_data,
|
||||
int last_n_tokens_size,
|
||||
const std::vector<float> logits,
|
||||
int top_k,
|
||||
double top_p,
|
||||
double temp,
|
||||
float repeat_penalty,
|
||||
std::mt19937 & rng) {
|
||||
int n_logits = actualVocabSize;
|
||||
|
||||
const auto last_n_tokens = std::vector<int32_t>(last_n_tokens_data, last_n_tokens_data + last_n_tokens_size);
|
||||
const auto * plogits = logits.data();
|
||||
|
||||
if (temp <= 0) {
|
||||
// select the token with the highest logit directly
|
||||
float max_logit = plogits[0];
|
||||
gpt_vocab::id max_id = 0;
|
||||
|
||||
for (int i = 1; i < n_logits; ++i) {
|
||||
if (plogits[i] > max_logit) {
|
||||
max_logit = plogits[i];
|
||||
max_id = i;
|
||||
}
|
||||
}
|
||||
return max_id;
|
||||
}
|
||||
std::vector<std::pair<double, gpt_vocab::id>> logits_id;
|
||||
logits_id.reserve(n_logits);
|
||||
|
||||
{
|
||||
const float scale = 1.0f/temp;
|
||||
for (int i = 0; i < n_logits; ++i) {
|
||||
// repetition penalty from ctrl paper (https://arxiv.org/abs/1909.05858)
|
||||
// credit https://github.com/facebookresearch/llama/compare/main...shawwn:llama:main
|
||||
if (std::find(last_n_tokens.begin(), last_n_tokens.end(), i) != last_n_tokens.end()) {
|
||||
// if score < 0 then repetition penalty has to multiplied to reduce the previous token probability
|
||||
if (plogits[i] < 0.0f) {
|
||||
logits_id.push_back(std::make_pair(plogits[i]*scale*repeat_penalty, i));
|
||||
} else {
|
||||
logits_id.push_back(std::make_pair(plogits[i]*scale/repeat_penalty, i));
|
||||
}
|
||||
} else {
|
||||
logits_id.push_back(std::make_pair(plogits[i]*scale, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find the top K tokens
|
||||
std::partial_sort(
|
||||
logits_id.begin(),
|
||||
logits_id.begin() + top_k, logits_id.end(),
|
||||
[](const std::pair<double, gpt_vocab::id> & a, const std::pair<double, gpt_vocab::id> & b) {
|
||||
return a.first > b.first;
|
||||
});
|
||||
|
||||
logits_id.resize(top_k);
|
||||
|
||||
double maxl = -INFINITY;
|
||||
for (const auto & kv : logits_id) {
|
||||
maxl = std::max(maxl, kv.first);
|
||||
}
|
||||
|
||||
// compute probs for the top K tokens
|
||||
std::vector<double> probs;
|
||||
probs.reserve(logits_id.size());
|
||||
|
||||
double sum = 0.0;
|
||||
for (const auto & kv : logits_id) {
|
||||
double p = exp(kv.first - maxl);
|
||||
probs.push_back(p);
|
||||
sum += p;
|
||||
}
|
||||
|
||||
// normalize the probs
|
||||
for (auto & p : probs) {
|
||||
p /= sum;
|
||||
}
|
||||
|
||||
if (top_p < 1.0f) {
|
||||
double cumsum = 0.0f;
|
||||
for (int i = 0; i < top_k; i++) {
|
||||
cumsum += probs[i];
|
||||
if (cumsum >= top_p) {
|
||||
top_k = i + 1;
|
||||
probs.resize(top_k);
|
||||
logits_id.resize(top_k);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cumsum = 1.0/cumsum;
|
||||
for (int i = 0; i < (int) probs.size(); i++) {
|
||||
probs[i] *= cumsum;
|
||||
}
|
||||
}
|
||||
|
||||
//printf("\n");
|
||||
//for (int i = 0; i < (int) probs.size(); i++) {
|
||||
// printf("%d: '%s' %f\n", i, vocab.id_to_token.at(logits_id[i].second).c_str(), probs[i]);
|
||||
//}
|
||||
//exit(0);
|
||||
|
||||
std::discrete_distribution<> dist(probs.begin(), probs.end());
|
||||
int idx = dist(rng);
|
||||
|
||||
return logits_id[idx].second;
|
||||
}
|
||||
@@ -1,17 +1,101 @@
|
||||
// Various helper functions and utilities
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#ifdef NDEBUG
|
||||
# ifdef __has_builtin
|
||||
# if __has_builtin(__builtin_unreachable)
|
||||
# define UNREACHABLE() __builtin_unreachable()
|
||||
# else
|
||||
# define UNREACHABLE() do {} while (0)
|
||||
# endif
|
||||
# else
|
||||
# define UNREACHABLE() do {} while (0)
|
||||
# endif
|
||||
#else
|
||||
# define UNREACHABLE() assert(!"Unreachable statement was reached")
|
||||
#endif
|
||||
//
|
||||
// General purpose inline functions
|
||||
//
|
||||
constexpr inline unsigned long long operator ""_MiB(unsigned long long bytes)
|
||||
{
|
||||
return bytes*1024*1024;
|
||||
}
|
||||
|
||||
//
|
||||
// CLI argument parsing
|
||||
//
|
||||
|
||||
struct gpt_params {
|
||||
int32_t seed = -1; // RNG seed
|
||||
int32_t n_threads = std::min(4, (int32_t) std::thread::hardware_concurrency());
|
||||
int32_t n_predict = 200; // new tokens to predict
|
||||
|
||||
// sampling parameters
|
||||
int32_t top_k = 40;
|
||||
float top_p = 0.9f;
|
||||
float temp = 0.9f;
|
||||
|
||||
int32_t n_batch = 8; // batch size for prompt processing
|
||||
|
||||
std::string model = "models/gpt-2-117M/ggml-model.bin"; // model path
|
||||
std::string prompt;
|
||||
};
|
||||
|
||||
bool gpt_params_parse(int argc, char ** argv, gpt_params & params);
|
||||
|
||||
void gpt_print_usage(int argc, char ** argv, const gpt_params & params);
|
||||
|
||||
std::string gpt_random_prompt(std::mt19937 & rng);
|
||||
|
||||
//
|
||||
// Vocab utils
|
||||
//
|
||||
|
||||
struct gpt_vocab {
|
||||
using id = int32_t;
|
||||
using token = std::string;
|
||||
|
||||
std::map<token, id> token_to_id;
|
||||
std::map<id, token> id_to_token;
|
||||
std::vector<std::string> special_tokens;
|
||||
|
||||
void add_special_token(const std::string &token) {
|
||||
special_tokens.push_back(token);
|
||||
}
|
||||
};
|
||||
|
||||
void replace(std::string & str, const std::string & needle, const std::string & replacement);
|
||||
|
||||
// poor-man's JSON parsing
|
||||
std::map<std::string, int32_t> json_parse(const std::string & fname);
|
||||
|
||||
// split text into tokens
|
||||
//
|
||||
// ref: https://github.com/openai/gpt-2/blob/a74da5d99abaaba920de8131d64da2862a8f213b/src/encoder.py#L53
|
||||
//
|
||||
// Regex (Python):
|
||||
// r"""'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+"""
|
||||
//
|
||||
// Regex (C++):
|
||||
// R"('s|'t|'re|'ve|'m|'ll|'d| ?[[:alpha:]]+| ?[[:digit:]]+| ?[^\s[:alpha:][:digit:]]+|\s+(?!\S)|\s+)"
|
||||
//
|
||||
std::vector<gpt_vocab::id> gpt_tokenize(const gpt_vocab & vocab, const std::string & text);
|
||||
|
||||
// load the tokens from encoder.json
|
||||
bool gpt_vocab_init(const std::string & fname, gpt_vocab & vocab);
|
||||
|
||||
// sample next token given probabilities for each embedding
|
||||
//
|
||||
// - consider only the top K tokens
|
||||
// - from them, consider only the top tokens with cumulative probability > P
|
||||
//
|
||||
// TODO: not sure if this implementation is correct
|
||||
//
|
||||
gpt_vocab::id gpt_sample_top_k_top_p(
|
||||
const size_t actualVocabSize,
|
||||
const int32_t * last_n_tokens_data,
|
||||
int last_n_tokens_size,
|
||||
const std::vector<float> logits,
|
||||
int top_k,
|
||||
double top_p,
|
||||
double temp,
|
||||
float repeat_penalty,
|
||||
std::mt19937 & rng);
|
||||
|
||||
@@ -113,7 +113,10 @@ def _old_loop(gpt4all_instance):
|
||||
full_response = gpt4all_instance.chat_completion(
|
||||
MESSAGES,
|
||||
# preferential kwargs for chat ux
|
||||
logits_size=0,
|
||||
tokens_size=0,
|
||||
n_past=0,
|
||||
n_ctx=0,
|
||||
n_predict=200,
|
||||
top_k=40,
|
||||
top_p=0.9,
|
||||
|
||||
@@ -8,14 +8,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
### Added
|
||||
- Warn on Windows if the Microsoft Visual C++ runtime libraries are not found ([#2920](https://github.com/nomic-ai/gpt4all/pull/2920))
|
||||
- Basic cache for faster prefill when the input shares a prefix with previous context ([#3073](https://github.com/nomic-ai/gpt4all/pull/3073))
|
||||
- Add ability to modify or replace the history of an active chat session ([#3147](https://github.com/nomic-ai/gpt4all/pull/3147))
|
||||
|
||||
### Changed
|
||||
- Rebase llama.cpp on latest upstream as of September 26th ([#2998](https://github.com/nomic-ai/gpt4all/pull/2998))
|
||||
- Change the error message when a message is too long ([#3004](https://github.com/nomic-ai/gpt4all/pull/3004))
|
||||
- Fix CalledProcessError on Intel Macs since v2.8.0 ([#3045](https://github.com/nomic-ai/gpt4all/pull/3045))
|
||||
- Use Jinja for chat templates instead of per-message QString.arg-style templates ([#3147](https://github.com/nomic-ai/gpt4all/pull/3147))
|
||||
|
||||
## [2.8.2] - 2024-08-14
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 272 KiB |
|
Before Width: | Height: | Size: 448 KiB |
@@ -1,86 +0,0 @@
|
||||
# GPT4All API Server
|
||||
|
||||
GPT4All provides a local API server that allows you to run LLMs over an HTTP API.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Local Execution**: Run models on your own hardware for privacy and offline use.
|
||||
- **LocalDocs Integration**: Run the API with relevant text snippets provided to your LLM from a [LocalDocs collection](../gpt4all_desktop/localdocs.md).
|
||||
- **OpenAI API Compatibility**: Use existing OpenAI-compatible clients and tools with your local models.
|
||||
|
||||
## Activating the API Server
|
||||
|
||||
1. Open the GPT4All Chat Desktop Application.
|
||||
2. Go to `Settings` > `Application` and scroll down to `Advanced`.
|
||||
3. Check the box for the `"Enable Local API Server"` setting.
|
||||
4. The server listens on port 4891 by default. You can choose another port number in the `"API Server Port"` setting.
|
||||
|
||||
## Connecting to the API Server
|
||||
|
||||
The base URL used for the API server is `http://localhost:4891/v1` (or `http://localhost:<PORT_NUM>/v1` if you are using a different port number).
|
||||
|
||||
The server only accepts HTTP connections (not HTTPS) and only listens on localhost (127.0.0.1) (e.g. not to the IPv6 localhost address `::1`.)
|
||||
|
||||
## Examples
|
||||
|
||||
!!! note "Example GPT4All API calls"
|
||||
|
||||
=== "cURL"
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:4891/v1/chat/completions -d '{
|
||||
"model": "Phi-3 Mini Instruct",
|
||||
"messages": [{"role":"user","content":"Who is Lionel Messi?"}],
|
||||
"max_tokens": 50,
|
||||
"temperature": 0.28
|
||||
}'
|
||||
```
|
||||
|
||||
=== "PowerShell"
|
||||
|
||||
```powershell
|
||||
Invoke-WebRequest -URI http://localhost:4891/v1/chat/completions -Method POST -ContentType application/json -Body '{
|
||||
"model": "Phi-3 Mini Instruct",
|
||||
"messages": [{"role":"user","content":"Who is Lionel Messi?"}],
|
||||
"max_tokens": 50,
|
||||
"temperature": 0.28
|
||||
}'
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/v1/models` | List available models |
|
||||
| GET | `/v1/models/<name>` | Get details of a specific model |
|
||||
| POST | `/v1/completions` | Generate text completions |
|
||||
| POST | `/v1/chat/completions` | Generate chat completions |
|
||||
|
||||
## LocalDocs Integration
|
||||
|
||||
You can use LocalDocs with the API server:
|
||||
|
||||
1. Open the Chats view in the GPT4All application.
|
||||
2. Scroll to the bottom of the chat history sidebar.
|
||||
3. Select the server chat (it has a different background color).
|
||||
4. Activate LocalDocs collections in the right sidebar.
|
||||
|
||||
(Note: LocalDocs can currently only be activated through the GPT4All UI, not via the API itself).
|
||||
|
||||
Now, your API calls to your local LLM will have relevant references from your LocalDocs collection retrieved and placed in the input message for the LLM to respond to.
|
||||
|
||||
The references retrieved for your API call can be accessed in the API response object at
|
||||
|
||||
`response["choices"][0]["references"]`
|
||||
|
||||
The data included in the `references` are:
|
||||
|
||||
- `text`: the actual text content from the snippet that was extracted from the reference document
|
||||
|
||||
- `author`: the author of the reference document (if available)
|
||||
|
||||
- `date`: the date of creation of the reference document (if available)
|
||||
|
||||
- `page`: the page number the snippet is from (only available for PDF documents for now)
|
||||
|
||||
- `title`: the title of the reference document (if available)
|
||||
@@ -1,206 +0,0 @@
|
||||
## What are chat templates?
|
||||
Natively, large language models only know how to complete plain text and do not know the difference between their input and their output. In order to support a chat with a person, LLMs are designed to use a template to convert the conversation to plain text using a specific format.
|
||||
|
||||
For a given model, it is important to use an appropriate chat template, as each model is designed to work best with a specific format. The chat templates included with the built-in models should be sufficient for most purposes.
|
||||
|
||||
There are two reasons you would want to alter the chat template:
|
||||
|
||||
- You are sideloading a model and there is no chat template available,
|
||||
- You would like to have greater control over the input to the LLM than a system message provides.
|
||||
|
||||
|
||||
## What is a system message?
|
||||
A system message is a message that controls the responses from the LLM in a way that affects the entire conversation. System messages can be short, such as "Speak like a pirate.", or they can be long and contain a lot of context for the LLM to keep in mind.
|
||||
|
||||
Not all models are designed to use a system message, so they work with some models better than others.
|
||||
|
||||
|
||||
## How do I customize the chat template or system message?
|
||||
To customize the chat template or system message, go to Settings > Model. Make sure to select the correct model at the top. If you clone a model, you can use a different chat template or system message from the base model, enabling you to use different settings for each conversation.
|
||||
|
||||
These settings take effect immediately. After changing them, you can click "Redo last response" in the chat view, and the response will take the new settings into account.
|
||||
|
||||
|
||||
## Do I need to write a chat template?
|
||||
You typically do not need to write your own chat template. The exception is models that are not in the official model list and do not come with a chat template built-in. These will show a "Clear" option above the chat template field in the Model Settings page instead of a "Reset" option. See the section on [finding] or [creating] a chat template.
|
||||
|
||||
[finding]: #how-do-i-find-a-chat-template
|
||||
[creating]: #advanced-how-do-chat-templates-work
|
||||
|
||||
|
||||
## What changed in GPT4All v3.5?
|
||||
GPT4All v3.5 overhauled the chat template system. There are three crucial differences:
|
||||
|
||||
- The chat template now formats an entire conversation instead of a single pair of messages,
|
||||
- The chat template now uses Jinja syntax instead of `%1` and `%2` placeholders,
|
||||
- And the system message should no longer contain control tokens or trailing whitespace.
|
||||
|
||||
If you are using any chat templates or system messages that had been added or altered from the default before upgrading to GPT4All v3.5 or newer, these will no longer work. See below for how to solve common errors you may see after upgrading.
|
||||
|
||||
|
||||
## Error/Warning: System message is not plain text.
|
||||
This is easy to fix. Go to the model's settings and look at the system prompt. There are three things to look for:
|
||||
|
||||
- Control tokens such as `<|im_start|>`, `<|start_header_id|>`, or `<|system|>`
|
||||
- A prefix such as `### System` or `SYSTEM:`
|
||||
- Trailing whitespace, such as a space character or blank line.
|
||||
|
||||
If you see any of these things, remove them. For example, this legacy system prompt:
|
||||
```
|
||||
<|start_header_id|>system<|end_header_id|>
|
||||
You are a helpful assistant.<|eot_id|>
|
||||
```
|
||||
|
||||
Should become this:
|
||||
```
|
||||
You are a helpful assistant.
|
||||
```
|
||||
|
||||
If you do not see anything that needs to be changed, you can dismiss the error by making a minor modification to the message and then changing it back.
|
||||
|
||||
If you see a warning, your system message does not appear to be plain text. If you believe this warning is incorrect, it can be safely ignored. If in doubt, ask on the [Discord].
|
||||
|
||||
[Discord]: https://discord.gg/mGZE39AS3e
|
||||
|
||||
|
||||
## Error: Legacy system prompt needs to be updated in Settings.
|
||||
This is the same as [above][above-1], but appears on the chat page.
|
||||
|
||||
[above-1]: #errorwarning-system-message-is-not-plain-text
|
||||
|
||||
|
||||
## Error/Warning: Chat template is not in Jinja format.
|
||||
This is the result of attempting to use an old-style template (possibly from a previous version) in GPT4All 3.5+.
|
||||
|
||||
Go to the Model Settings page and select the affected model. If you see a "Reset" button, and you have not intentionally modified the prompt template, you can click "Reset". Otherwise, this is what you can do:
|
||||
|
||||
1. Back up your chat template by copying it safely to a text file and saving it. In the next step, it will be removed from GPT4All.
|
||||
2. Click "Reset" or "Clear".
|
||||
3. If you clicked "Clear", the chat template is now gone. Follow the steps to [find][finding] or [create][creating] a basic chat template for your model.
|
||||
4. Customize the chat template to suit your needs. For help, read the section about [creating] a chat template.
|
||||
|
||||
|
||||
## Error: Legacy prompt template needs to be updated in Settings.
|
||||
This is the same as [above][above-2], but appears on the chat page.
|
||||
|
||||
[above-2]: #errorwarning-chat-template-is-not-in-jinja-format
|
||||
|
||||
|
||||
## The chat template has a syntax error.
|
||||
If there is a syntax error while editing the chat template, the details will be displayed in an error message above the input box. This could be because the chat template is not actually in Jinja format (see [above][above-2]).
|
||||
|
||||
Otherwise, you have either typed something correctly, or the model comes with a template that is incompatible with GPT4All. See [the below section][creating] on creating chat templates and make sure that everything is correct. When in doubt, ask on the [Discord].
|
||||
|
||||
|
||||
## Error: No chat template configured.
|
||||
This may appear for models that are not from the official model list and do not include a chat template. Older versions of GPT4All picked a poor default in this case. You will get much better results if you follow the steps to [find][finding] or [create][creating] a chat template for your model.
|
||||
|
||||
|
||||
## Error: The chat template cannot be blank.
|
||||
If the button above the chat template on the Model Settings page says "Clear", see [above][above-3]. If you see "Reset", click that button to restore a reasonable default. Also see the section on [syntax errors][chat-syntax-error].
|
||||
|
||||
[above-3]: #error-no-chat-template-configured
|
||||
[chat-syntax-error]: #the-chat-template-has-a-syntax-error
|
||||
|
||||
|
||||
## How do I find a chat template?
|
||||
When in doubt, you can always ask the [Discord] community for help. Below are the instructions to find one on your own.
|
||||
|
||||
The authoritative source for a model's chat template is the HuggingFace repo that the original (non-GGUF) model came from. First, you should find this page. If you just have a model file, you can try a google search for the model's name. If you know the page you downloaded the GGUF model from, its README usually links to the original non-GGUF model.
|
||||
|
||||
Once you have located the original model, there are two methods you can use to extract its chat template. Pick whichever one you are most comfortable with.
|
||||
|
||||
### Using the CLI (all models)
|
||||
1. Install `jq` using your preferred package manager - e.g. Chocolatey (Windows), Homebrew (macOS), or apt (Ubuntu).
|
||||
2. Download `tokenizer_config.json` from the model's "Files and versions" tab.
|
||||
3. Open a command prompt in the directory which you have downloaded the model file.
|
||||
4. Run `jq -r ".chat_template" tokenizer_config.json`. This shows the chat template in a human-readable form. You can copy this and paste it into the settings page.
|
||||
5. (Optional) You can save the output to a text file like this: `jq -r ".chat_template" tokenizer_config.json >chat_template.txt`
|
||||
|
||||
If the output is "null", the model does not provide a chat template. See the [below instructions][creating] on creating a chat template.
|
||||
|
||||
### Python (open models)
|
||||
1. Install `transformers` using your preferred python package manager, e.g. `pip install transformers`. Make sure it is at least version v4.43.0.
|
||||
2. Copy the ID of the HuggingFace model, using the clipboard icon next to the name. For example, if the URL is `https://huggingface.co/NousResearch/Hermes-2-Pro-Llama-3-8B`, the ID is `NousResearch/Hermes-2-Pro-Llama-3-8B`.
|
||||
3. Open a python interpreter (`python`) and run the following commands. Change the model ID in the example to the one you copied.
|
||||
```
|
||||
>>> from transformers import AutoTokenizer
|
||||
>>> tokenizer = AutoTokenizer.from_pretrained('NousResearch/Hermes-2-Pro-Llama-3-8B')
|
||||
>>> print(tokenizer.get_chat_template())
|
||||
```
|
||||
You can copy the output and paste it into the settings page.
|
||||
4. (Optional) You can save the output to a text file like this:
|
||||
```
|
||||
>>> open('chat_template.txt', 'w').write(tokenizer.get_chat_template())
|
||||
```
|
||||
|
||||
If you get a ValueError exception, this model does not provide a chat template. See the [below instructions][creating] on creating a chat template.
|
||||
|
||||
|
||||
### Python (gated models)
|
||||
Some models, such as Llama and Mistral, do not allow public access to their chat template. You must either use the CLI method above, or follow the following instructions to use Python:
|
||||
|
||||
1. For these steps, you must have git and git-lfs installed.
|
||||
2. You must have a HuggingFace account and be logged in.
|
||||
3. You must already have access to the gated model. Otherwise, request access.
|
||||
4. You must have an SSH key configured for git access to HuggingFace.
|
||||
5. `git clone` the model's HuggingFace repo using the SSH clone URL. There is no need to download the entire model, which is very large. A good way to do this on Linux is:
|
||||
```console
|
||||
$ GIT_LFS_SKIP_SMUDGE=1 git clone hf.co:meta-llama/Llama-3.1-8B-Instruct.git
|
||||
$ cd Llama-3.1-8B-Instruct
|
||||
$ git lfs pull -I "tokenizer.*"
|
||||
```
|
||||
6. Follow the above instructions for open models, but replace the model ID with the path to the directory containing `tokenizer\_config.json`:
|
||||
```
|
||||
>>> tokenizer = AutoTokenizer.from_pretrained('.')
|
||||
```
|
||||
|
||||
|
||||
## Advanced: How do chat templates work?
|
||||
The chat template is applied to the entire conversation you see in the chat window. The template loops over the list of messages, each containing `role` and `content` fields. `role` is either `user`, `assistant`, or `system`.
|
||||
|
||||
GPT4All also supports the special variables `bos_token`, `eos_token`, and `add_generation_prompt`. See the [HuggingFace docs] for what those do.
|
||||
|
||||
[HuggingFace docs]: https://huggingface.co/docs/transformers/v4.46.3/en/chat_templating#special-variables
|
||||
|
||||
|
||||
## Advanced: How do I make a chat template?
|
||||
The best way to create a chat template is to start by using an existing one as a reference. Then, modify it to use the format documented for the given model. Its README page may explicitly give an example of its template. Or, it may mention the name of a well-known standard template, such as ChatML, Alpaca, Vicuna. GPT4All does not yet include presets for these templates, so they will have to be found in other models or taken from the community.
|
||||
|
||||
For more information, see the very helpful [HuggingFace guide]. Some of this is not applicable, such as the information about tool calling and RAG - GPT4All implements those features differently.
|
||||
|
||||
Some models use a prompt template that does not intuitively map to a multi-turn chat, because it is more intended for single instructions. The [FastChat] implementation of these templates is a useful reference for the correct way to extend them to multiple messages.
|
||||
|
||||
[HuggingFace guide]: https://huggingface.co/docs/transformers/v4.46.3/en/chat_templating#advanced-template-writing-tips
|
||||
[FastChat]: https://github.com/lm-sys/FastChat/blob/main/fastchat/conversation.py
|
||||
|
||||
|
||||
# Advanced: What are GPT4All v1 templates?
|
||||
GPT4All supports its own template syntax, which is nonstandard but provides complete control over the way LocalDocs sources and file attachments are inserted into the conversation. These templates begin with `{# gpt4all v1 #}` and look similar to the example below.
|
||||
|
||||
For standard templates, GPT4All combines the user message, sources, and attachments into the `content` field. For GPT4All v1 templates, this is not done, so they must be used directly in the template for those features to work correctly.
|
||||
|
||||
```jinja
|
||||
{# gpt4all v1 #}
|
||||
{%- for message in messages %}
|
||||
{{- '<|start_header_id|>' + message['role'] + '<|end_header_id|>\n\n' }}
|
||||
{%- if message['role'] == 'user' %}
|
||||
{%- for source in message['sources'] %}
|
||||
{%- if loop.first %}
|
||||
{{- '### Context:\n' }}
|
||||
{%- endif %}
|
||||
{{- 'Collection: ' + source['collection'] + '\n' +
|
||||
'Path: ' + source['path'] + '\n' +
|
||||
'Excerpt: ' + source['text'] + '\n\n' }}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{%- for attachment in message['prompt_attachments'] %}
|
||||
{{- attachment['processed_content'] + '\n\n' }}
|
||||
{%- endfor %}
|
||||
{{- message['content'] | trim }}
|
||||
{{- '<|eot_id|>' }}
|
||||
{%- endfor %}
|
||||
{%- if add_generation_prompt %}
|
||||
{{- '<|start_header_id|>assistant<|end_header_id|>\n\n' }}
|
||||
{%- endif %}
|
||||
```
|
||||
@@ -1,85 +0,0 @@
|
||||
# Using GPT4All to Privately Chat with your Microsoft Excel Spreadsheets
|
||||
Local and Private AI Chat with your Microsoft Excel Spreadsheets
|
||||
|
||||
Microsoft Excel allows you to create, manage, and analyze data in spreadsheet format. By attaching your spreadsheets directly to GPT4All, you can privately chat with the AI to query and explore the data, enabling you to summarize, generate reports, and glean insights from your files—all within your conversation.
|
||||
|
||||
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
|
||||
<iframe src="../../assets/gpt4all_xlsx_attachment.mp4" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
|
||||
</div>
|
||||
|
||||
|
||||
## Attach Microsoft Excel to your GPT4All Conversation
|
||||
|
||||
!!! note "Attach Microsoft Excel to your GPT4All Conversation"
|
||||
|
||||
1. **Install GPT4All and Open **:
|
||||
|
||||
- Go to [nomic.ai/gpt4all](https://nomic.ai/gpt4all) to install GPT4All for your operating system.
|
||||
|
||||
- Navigate to the Chats view within GPT4All.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<!-- Screenshot of Chat view -->
|
||||
<img width="1348" alt="Chat view" src="../../assets/chat_window.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
2. **Example Spreadsheet **:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<!-- Screenshot of Spreadsheet view -->
|
||||
<img width="1348" alt="Spreadsheet view" src="../../assets/disney_spreadsheet.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
3. **Attach to GPT4All conversration**
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<!-- Screenshot of Attach view -->
|
||||
<img width="1348" alt="Attach view" src="../../assets/attach_spreadsheet.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
4. **Have GPT4All Summarize and Generate a Report**
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<!-- Screenshot of Attach view -->
|
||||
<img width="1348" alt="Attach view" src="../../assets/spreadsheet_chat.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
## How It Works
|
||||
|
||||
GPT4All parses your attached excel spreadsheet into Markdown, a format understandable to LLMs, and adds the markdown text to the context for your LLM chat. You can view the code that converts `.xslx` to Markdown [here](https://github.com/nomic-ai/gpt4all/blob/main/gpt4all-chat/src/xlsxtomd.cpp) in the GPT4All github repo.
|
||||
|
||||
For example, the above spreadsheet titled `disney_income_stmt.xlsx` would be formatted the following way:
|
||||
|
||||
```markdown
|
||||
## disney_income_stmt
|
||||
|
||||
|Walt Disney Co.|||||||
|
||||
|---|---|---|---|---|---|---|
|
||||
|Consolidated Income Statement|||||||
|
||||
|||||||||
|
||||
|US$ in millions|||||||
|
||||
|12 months ended:|2023-09-30 00:00:00|2022-10-01 00:00:00|2021-10-02 00:00:00|2020-10-03 00:00:00|2019-09-28 00:00:00|2018-09-29 00:00:00|
|
||||
|Services|79562|74200|61768|59265|60542|50869|
|
||||
...
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
It is important to double-check the claims LLMs make about the spreadsheets you provide. LLMs can make mistakes about the data they are presented, particularly for the LLMs with smaller parameter counts (~8B) that fit within the memory of consumer hardware.
|
||||
@@ -8,11 +8,10 @@
|
||||
| --- | --- | --- |
|
||||
| **Theme** | Color theme for the application. Options are `Light`, `Dark`, and `LegacyDark` | `Light` |
|
||||
| **Font Size** | Font size setting for text throughout the application. Options are Small, Medium, and Large | Small |
|
||||
| **Language and Locale** | The language and locale of that language you wish to use | System Locale |
|
||||
| **Device** | Device that will run your models. Options are `Auto` (GPT4All chooses), `Metal` (Apple Silicon M1+), `CPU`, and `GPU` | `Auto` |
|
||||
| **Default Model** | Choose your preferred LLM to load by default on startup| Auto |
|
||||
| **Suggestion Mode** | Generate suggested follow up questions at the end of responses | When chatting with LocalDocs |
|
||||
| **Download Path** | Select a destination on your device to save downloaded models | Windows: `C:\Users\{username}\AppData\Local\nomic.ai\GPT4All`<br><br>Mac: `/Users/{username}/Library/Application Support/nomic.ai/GPT4All/`<br><br>Linux: `/home/{username}/.local/share/nomic.ai/GPT4All` |
|
||||
|
||||
| **Enable Datalake** | Opt-in to sharing interactions with GPT4All community (**anonymous** and **optional**) | Off |
|
||||
|
||||
!!! note "Advanced Application Settings"
|
||||
@@ -20,7 +19,7 @@
|
||||
| Setting | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| **CPU Threads** | Number of concurrently running CPU threads (more can speed up responses) | 4 |
|
||||
| **Enable System Tray** | The application will minimize to the system tray / taskbar when the window is closed | Off |
|
||||
| **Save Chat Context** | Save chat context to disk to pick up exactly where a model left off. | Off |
|
||||
| **Enable Local Server** | Allow any application on your device to use GPT4All via an OpenAI-compatible GPT4All API | Off |
|
||||
| **API Server Port** | Local HTTP port for the local API server | 4891 |
|
||||
|
||||
@@ -31,11 +30,8 @@
|
||||
| Setting | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| **Name** | Unique name of this model / character| set by model uploader |
|
||||
| **Model File** | Filename (.gguf) of the model | set by model uploader |
|
||||
| **System Message** | General instructions for the chats this model will be used for | set by model uploader |
|
||||
| **Chat Template** | Format of user <-> assistant interactions for the chats this model will be used for | set by model uploader |
|
||||
| **Chat Name Prompt** | Prompt used to automatically generate chat names | Describe the above conversation in seven words or less. |
|
||||
| **Suggested FollowUp Prompt** | Prompt used to automatically generate follow up questions after a chat response | Suggest three very short factual follow-up questions that have not been answered yet or cannot be found inspired by the previous conversation and excerpts. |
|
||||
| **System Prompt** | General instructions for the chats this model will be used for | set by model uploader |
|
||||
| **Prompt Template** | Format of user <-> assistant interactions for the chats this model will be used for | set by model uploader |
|
||||
|
||||
### Clone
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@ from __future__ import annotations
|
||||
import ctypes
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
import threading
|
||||
from enum import Enum
|
||||
from queue import Queue
|
||||
from typing import TYPE_CHECKING, Any, Callable, Generic, Iterable, Iterator, Literal, NoReturn, TypeVar, overload
|
||||
from typing import TYPE_CHECKING, Any, Callable, Generic, Iterable, Literal, NoReturn, TypeVar, overload
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
import importlib.resources as importlib_resources
|
||||
@@ -23,31 +24,20 @@ else:
|
||||
from typing import TypedDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import ParamSpec, TypeAlias
|
||||
T = TypeVar("T")
|
||||
P = ParamSpec("P")
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
EmbeddingsType = TypeVar('EmbeddingsType', bound='list[Any]')
|
||||
|
||||
cuda_found: bool = False
|
||||
|
||||
|
||||
# TODO(jared): use operator.call after we drop python 3.10 support
|
||||
def _operator_call(obj: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs) -> T:
|
||||
return obj(*args, **kwargs)
|
||||
|
||||
|
||||
# Detect Rosetta 2
|
||||
@_operator_call
|
||||
def check_rosetta() -> None:
|
||||
if platform.system() == "Darwin" and platform.processor() == "i386":
|
||||
p = subprocess.run("sysctl -n sysctl.proc_translated".split(), capture_output=True, text=True)
|
||||
if p.returncode == 0 and p.stdout.strip() == "1":
|
||||
raise RuntimeError(textwrap.dedent("""\
|
||||
Running GPT4All under Rosetta is not supported due to CPU feature requirements.
|
||||
Please install GPT4All in an environment that uses a native ARM64 Python interpreter.
|
||||
""").strip())
|
||||
|
||||
if platform.system() == "Darwin" and platform.processor() == "i386":
|
||||
if subprocess.run(
|
||||
"sysctl -n sysctl.proc_translated".split(), check=True, capture_output=True, text=True,
|
||||
).stdout.strip() == "1":
|
||||
raise RuntimeError(textwrap.dedent("""\
|
||||
Running GPT4All under Rosetta is not supported due to CPU feature requirements.
|
||||
Please install GPT4All in an environment that uses a native ARM64 Python interpreter.
|
||||
""").strip())
|
||||
|
||||
# Check for C++ runtime libraries
|
||||
if platform.system() == "Windows":
|
||||
@@ -63,35 +53,33 @@ if platform.system() == "Windows":
|
||||
"""), file=sys.stderr)
|
||||
|
||||
|
||||
@_operator_call
|
||||
def find_cuda() -> None:
|
||||
global cuda_found
|
||||
def _load_cuda(rtver: str, blasver: str) -> None:
|
||||
if platform.system() == "Linux":
|
||||
cudalib = f"lib/libcudart.so.{rtver}"
|
||||
cublaslib = f"lib/libcublas.so.{blasver}"
|
||||
else: # Windows
|
||||
cudalib = fr"bin\cudart64_{rtver.replace('.', '')}.dll"
|
||||
cublaslib = fr"bin\cublas64_{blasver}.dll"
|
||||
|
||||
def _load_cuda(rtver: str, blasver: str) -> None:
|
||||
if platform.system() == "Linux":
|
||||
cudalib = f"lib/libcudart.so.{rtver}"
|
||||
cublaslib = f"lib/libcublas.so.{blasver}"
|
||||
else: # Windows
|
||||
cudalib = fr"bin\cudart64_{rtver.replace('.', '')}.dll"
|
||||
cublaslib = fr"bin\cublas64_{blasver}.dll"
|
||||
# preload the CUDA libs so the backend can find them
|
||||
ctypes.CDLL(os.path.join(cuda_runtime.__path__[0], cudalib), mode=ctypes.RTLD_GLOBAL)
|
||||
ctypes.CDLL(os.path.join(cublas.__path__[0], cublaslib), mode=ctypes.RTLD_GLOBAL)
|
||||
|
||||
# preload the CUDA libs so the backend can find them
|
||||
ctypes.CDLL(os.path.join(cuda_runtime.__path__[0], cudalib), mode=ctypes.RTLD_GLOBAL)
|
||||
ctypes.CDLL(os.path.join(cublas.__path__[0], cublaslib), mode=ctypes.RTLD_GLOBAL)
|
||||
|
||||
# Find CUDA libraries from the official packages
|
||||
if platform.system() in ("Linux", "Windows"):
|
||||
try:
|
||||
from nvidia import cuda_runtime, cublas
|
||||
except ImportError:
|
||||
pass # CUDA is optional
|
||||
else:
|
||||
for rtver, blasver in [("12", "12"), ("11.0", "11")]:
|
||||
try:
|
||||
_load_cuda(rtver, blasver)
|
||||
cuda_found = True
|
||||
except OSError: # dlopen() does not give specific error codes
|
||||
pass # try the next one
|
||||
# Find CUDA libraries from the official packages
|
||||
cuda_found = False
|
||||
if platform.system() in ("Linux", "Windows"):
|
||||
try:
|
||||
from nvidia import cuda_runtime, cublas
|
||||
except ImportError:
|
||||
pass # CUDA is optional
|
||||
else:
|
||||
for rtver, blasver in [("12", "12"), ("11.0", "11")]:
|
||||
try:
|
||||
_load_cuda(rtver, blasver)
|
||||
cuda_found = True
|
||||
except OSError: # dlopen() does not give specific error codes
|
||||
pass # try the next one
|
||||
|
||||
|
||||
# TODO: provide a config file to make this more robust
|
||||
@@ -118,18 +106,21 @@ llmodel = load_llmodel_library()
|
||||
|
||||
class LLModelPromptContext(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("n_predict", ctypes.c_int32),
|
||||
("top_k", ctypes.c_int32),
|
||||
("top_p", ctypes.c_float),
|
||||
("min_p", ctypes.c_float),
|
||||
("temp", ctypes.c_float),
|
||||
("n_batch", ctypes.c_int32),
|
||||
("tokens", ctypes.POINTER(ctypes.c_int32)),
|
||||
("tokens_size", ctypes.c_size_t),
|
||||
("n_past", ctypes.c_int32),
|
||||
("n_ctx", ctypes.c_int32),
|
||||
("n_predict", ctypes.c_int32),
|
||||
("top_k", ctypes.c_int32),
|
||||
("top_p", ctypes.c_float),
|
||||
("min_p", ctypes.c_float),
|
||||
("temp", ctypes.c_float),
|
||||
("n_batch", ctypes.c_int32),
|
||||
("repeat_penalty", ctypes.c_float),
|
||||
("repeat_last_n", ctypes.c_int32),
|
||||
("context_erase", ctypes.c_float),
|
||||
("repeat_last_n", ctypes.c_int32),
|
||||
("context_erase", ctypes.c_float),
|
||||
]
|
||||
|
||||
|
||||
class LLModelGPUDevice(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("backend", ctypes.c_char_p),
|
||||
@@ -140,7 +131,6 @@ class LLModelGPUDevice(ctypes.Structure):
|
||||
("vendor", ctypes.c_char_p),
|
||||
]
|
||||
|
||||
|
||||
# Define C function signatures using ctypes
|
||||
llmodel.llmodel_model_create.argtypes = [ctypes.c_char_p]
|
||||
llmodel.llmodel_model_create.restype = ctypes.c_void_p
|
||||
@@ -158,21 +148,23 @@ llmodel.llmodel_required_mem.restype = ctypes.c_size_t
|
||||
llmodel.llmodel_isModelLoaded.argtypes = [ctypes.c_void_p]
|
||||
llmodel.llmodel_isModelLoaded.restype = ctypes.c_bool
|
||||
|
||||
PromptCallback = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.POINTER(ctypes.c_int32), ctypes.c_size_t, ctypes.c_bool)
|
||||
ResponseCallback = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_int32, ctypes.c_char_p)
|
||||
EmbCancelCallback = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.POINTER(ctypes.c_uint), ctypes.c_uint, ctypes.c_char_p)
|
||||
SpecialTokenCallback = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_char_p)
|
||||
PromptCallback = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_int32)
|
||||
ResponseCallback = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_int32, ctypes.c_char_p)
|
||||
EmbCancelCallback = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.POINTER(ctypes.c_uint), ctypes.c_uint, ctypes.c_char_p)
|
||||
|
||||
llmodel.llmodel_prompt.argtypes = [
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_char_p,
|
||||
ctypes.c_char_p,
|
||||
PromptCallback,
|
||||
ResponseCallback,
|
||||
ctypes.c_bool,
|
||||
ctypes.POINTER(LLModelPromptContext),
|
||||
ctypes.POINTER(ctypes.c_char_p),
|
||||
ctypes.c_bool,
|
||||
ctypes.c_char_p,
|
||||
]
|
||||
|
||||
llmodel.llmodel_prompt.restype = ctypes.c_bool
|
||||
llmodel.llmodel_prompt.restype = None
|
||||
|
||||
llmodel.llmodel_embed.argtypes = [
|
||||
ctypes.c_void_p,
|
||||
@@ -221,12 +213,6 @@ llmodel.llmodel_model_backend_name.restype = ctypes.c_char_p
|
||||
llmodel.llmodel_model_gpu_device_name.argtypes = [ctypes.c_void_p]
|
||||
llmodel.llmodel_model_gpu_device_name.restype = ctypes.c_char_p
|
||||
|
||||
llmodel.llmodel_count_prompt_tokens.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_char_p)]
|
||||
llmodel.llmodel_count_prompt_tokens.restype = ctypes.c_int32
|
||||
|
||||
llmodel.llmodel_model_foreach_special_token.argtypes = [ctypes.c_void_p, SpecialTokenCallback]
|
||||
llmodel.llmodel_model_foreach_special_token.restype = None
|
||||
|
||||
ResponseCallbackType = Callable[[int, str], bool]
|
||||
RawResponseCallbackType = Callable[[int, bytes], bool]
|
||||
EmbCancelCallbackType: TypeAlias = 'Callable[[list[int], str], bool]'
|
||||
@@ -271,6 +257,7 @@ class LLModel:
|
||||
self.model_path = model_path.encode()
|
||||
self.n_ctx = n_ctx
|
||||
self.ngl = ngl
|
||||
self.context: LLModelPromptContext | None = None
|
||||
self.buffer = bytearray()
|
||||
self.buff_expecting_cont_bytes: int = 0
|
||||
|
||||
@@ -290,10 +277,6 @@ class LLModel:
|
||||
|
||||
raise RuntimeError(f"Unable to instantiate model: {errmsg}")
|
||||
self.model: ctypes.c_void_p | None = model
|
||||
self.special_tokens_map: dict[str, str] = {}
|
||||
llmodel.llmodel_model_foreach_special_token(
|
||||
self.model, lambda n, t: self.special_tokens_map.__setitem__(n.decode(), t.decode()),
|
||||
)
|
||||
|
||||
def __del__(self, llmodel=llmodel):
|
||||
if hasattr(self, 'model'):
|
||||
@@ -320,19 +303,6 @@ class LLModel:
|
||||
dev = llmodel.llmodel_model_gpu_device_name(self.model)
|
||||
return None if dev is None else dev.decode()
|
||||
|
||||
def count_prompt_tokens(self, prompt: str) -> int:
|
||||
if self.model is None:
|
||||
self._raise_closed()
|
||||
err = ctypes.c_char_p()
|
||||
n_tok = llmodel.llmodel_count_prompt_tokens(self.model, prompt, ctypes.byref(err))
|
||||
if n_tok < 0:
|
||||
s = err.value
|
||||
errmsg = 'null' if s is None else s.decode()
|
||||
raise RuntimeError(f'Unable to count prompt tokens: {errmsg}')
|
||||
return n_tok
|
||||
|
||||
llmodel.llmodel_count_prompt_tokens.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
|
||||
|
||||
@staticmethod
|
||||
def list_gpus(mem_required: int = 0) -> list[str]:
|
||||
"""
|
||||
@@ -396,6 +366,50 @@ class LLModel:
|
||||
raise Exception("Model not loaded")
|
||||
return llmodel.llmodel_threadCount(self.model)
|
||||
|
||||
def _set_context(
|
||||
self,
|
||||
n_predict: int = 4096,
|
||||
top_k: int = 40,
|
||||
top_p: float = 0.9,
|
||||
min_p: float = 0.0,
|
||||
temp: float = 0.1,
|
||||
n_batch: int = 8,
|
||||
repeat_penalty: float = 1.2,
|
||||
repeat_last_n: int = 10,
|
||||
context_erase: float = 0.75,
|
||||
reset_context: bool = False,
|
||||
):
|
||||
if self.context is None:
|
||||
context = LLModelPromptContext(
|
||||
tokens_size=0,
|
||||
n_past=0,
|
||||
n_ctx=0,
|
||||
n_predict=n_predict,
|
||||
top_k=top_k,
|
||||
top_p=top_p,
|
||||
min_p=min_p,
|
||||
temp=temp,
|
||||
n_batch=n_batch,
|
||||
repeat_penalty=repeat_penalty,
|
||||
repeat_last_n=repeat_last_n,
|
||||
context_erase=context_erase,
|
||||
)
|
||||
self.context = context
|
||||
else:
|
||||
context = self.context
|
||||
if reset_context:
|
||||
self.context.n_past = 0
|
||||
|
||||
self.context.n_predict = n_predict
|
||||
self.context.top_k = top_k
|
||||
self.context.top_p = top_p
|
||||
self.context.min_p = min_p
|
||||
self.context.temp = temp
|
||||
self.context.n_batch = n_batch
|
||||
self.context.repeat_penalty = repeat_penalty
|
||||
self.context.repeat_last_n = repeat_last_n
|
||||
self.context.context_erase = context_erase
|
||||
|
||||
@overload
|
||||
def generate_embeddings(
|
||||
self, text: str, prefix: str | None, dimensionality: int, do_mean: bool, atlas: bool,
|
||||
@@ -465,18 +479,20 @@ class LLModel:
|
||||
|
||||
def prompt_model(
|
||||
self,
|
||||
prompt : str,
|
||||
callback : ResponseCallbackType,
|
||||
n_predict : int = 4096,
|
||||
top_k : int = 40,
|
||||
top_p : float = 0.9,
|
||||
min_p : float = 0.0,
|
||||
temp : float = 0.1,
|
||||
n_batch : int = 8,
|
||||
repeat_penalty : float = 1.2,
|
||||
repeat_last_n : int = 10,
|
||||
context_erase : float = 0.75,
|
||||
reset_context : bool = False,
|
||||
prompt: str,
|
||||
prompt_template: str,
|
||||
callback: ResponseCallbackType,
|
||||
n_predict: int = 4096,
|
||||
top_k: int = 40,
|
||||
top_p: float = 0.9,
|
||||
min_p: float = 0.0,
|
||||
temp: float = 0.1,
|
||||
n_batch: int = 8,
|
||||
repeat_penalty: float = 1.2,
|
||||
repeat_last_n: int = 10,
|
||||
context_erase: float = 0.75,
|
||||
reset_context: bool = False,
|
||||
special: bool = False,
|
||||
):
|
||||
"""
|
||||
Generate response from model from a prompt.
|
||||
@@ -499,38 +515,35 @@ class LLModel:
|
||||
self.buffer.clear()
|
||||
self.buff_expecting_cont_bytes = 0
|
||||
|
||||
context = LLModelPromptContext(
|
||||
n_predict = n_predict,
|
||||
top_k = top_k,
|
||||
top_p = top_p,
|
||||
min_p = min_p,
|
||||
temp = temp,
|
||||
n_batch = n_batch,
|
||||
repeat_penalty = repeat_penalty,
|
||||
repeat_last_n = repeat_last_n,
|
||||
context_erase = context_erase,
|
||||
self._set_context(
|
||||
n_predict=n_predict,
|
||||
top_k=top_k,
|
||||
top_p=top_p,
|
||||
min_p=min_p,
|
||||
temp=temp,
|
||||
n_batch=n_batch,
|
||||
repeat_penalty=repeat_penalty,
|
||||
repeat_last_n=repeat_last_n,
|
||||
context_erase=context_erase,
|
||||
reset_context=reset_context,
|
||||
)
|
||||
|
||||
error_msg: bytes | None = None
|
||||
def error_callback(msg: bytes) -> None:
|
||||
nonlocal error_msg
|
||||
error_msg = msg
|
||||
|
||||
err = ctypes.c_char_p()
|
||||
if not llmodel.llmodel_prompt(
|
||||
llmodel.llmodel_prompt(
|
||||
self.model,
|
||||
ctypes.c_char_p(prompt.encode()),
|
||||
ctypes.c_char_p(prompt_template.encode()),
|
||||
PromptCallback(self._prompt_callback),
|
||||
ResponseCallback(self._callback_decoder(callback)),
|
||||
context,
|
||||
ctypes.byref(err),
|
||||
):
|
||||
s = err.value
|
||||
raise RuntimeError(f"prompt error: {'null' if s is None else s.decode()}")
|
||||
True,
|
||||
self.context,
|
||||
special,
|
||||
ctypes.c_char_p(),
|
||||
)
|
||||
|
||||
|
||||
def prompt_model_streaming(
|
||||
self, prompt: str, callback: ResponseCallbackType = empty_response_callback, **kwargs: Any,
|
||||
) -> Iterator[str]:
|
||||
self, prompt: str, prompt_template: str, callback: ResponseCallbackType = empty_response_callback, **kwargs
|
||||
) -> Iterable[str]:
|
||||
if self.model is None:
|
||||
self._raise_closed()
|
||||
|
||||
@@ -549,15 +562,15 @@ class LLModel:
|
||||
|
||||
return _generator_callback
|
||||
|
||||
def run_llmodel_prompt(prompt: str, callback: ResponseCallbackType, **kwargs):
|
||||
self.prompt_model(prompt, callback, **kwargs)
|
||||
def run_llmodel_prompt(prompt: str, prompt_template: str, callback: ResponseCallbackType, **kwargs):
|
||||
self.prompt_model(prompt, prompt_template, callback, **kwargs)
|
||||
output_queue.put(Sentinel.TERMINATING_SYMBOL)
|
||||
|
||||
# Kick off llmodel_prompt in separate thread so we can return generator
|
||||
# immediately
|
||||
thread = threading.Thread(
|
||||
target=run_llmodel_prompt,
|
||||
args=(prompt, _generator_callback_wrapper(callback)),
|
||||
args=(prompt, prompt_template, _generator_callback_wrapper(callback)),
|
||||
kwargs=kwargs,
|
||||
)
|
||||
thread.start()
|
||||
@@ -576,16 +589,16 @@ class LLModel:
|
||||
decoded = []
|
||||
|
||||
for byte in response:
|
||||
|
||||
|
||||
bits = "{:08b}".format(byte)
|
||||
(high_ones, _, _) = bits.partition('0')
|
||||
|
||||
if len(high_ones) == 1:
|
||||
if len(high_ones) == 1:
|
||||
# continuation byte
|
||||
self.buffer.append(byte)
|
||||
self.buff_expecting_cont_bytes -= 1
|
||||
|
||||
else:
|
||||
else:
|
||||
# beginning of a byte sequence
|
||||
if len(self.buffer) > 0:
|
||||
decoded.append(self.buffer.decode(errors='replace'))
|
||||
@@ -595,22 +608,22 @@ class LLModel:
|
||||
self.buffer.append(byte)
|
||||
self.buff_expecting_cont_bytes = max(0, len(high_ones) - 1)
|
||||
|
||||
if self.buff_expecting_cont_bytes <= 0:
|
||||
if self.buff_expecting_cont_bytes <= 0:
|
||||
# received the whole sequence or an out of place continuation byte
|
||||
decoded.append(self.buffer.decode(errors='replace'))
|
||||
|
||||
self.buffer.clear()
|
||||
self.buff_expecting_cont_bytes = 0
|
||||
|
||||
|
||||
if len(decoded) == 0 and self.buff_expecting_cont_bytes > 0:
|
||||
# wait for more continuation bytes
|
||||
return True
|
||||
|
||||
return callback(token_id, ''.join(decoded))
|
||||
|
||||
return callback(token_id, ''.join(decoded))
|
||||
|
||||
return _raw_callback
|
||||
|
||||
# Empty prompt callback
|
||||
@staticmethod
|
||||
def _prompt_callback(token_ids: ctypes._Pointer[ctypes.c_int32], n_token_ids: int, cached: bool) -> bool:
|
||||
def _prompt_callback(token_id: int) -> bool:
|
||||
return True
|
||||
|
||||
@@ -4,66 +4,38 @@ Python only API for running all GPT4All models.
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from types import TracebackType
|
||||
from typing import TYPE_CHECKING, Any, Iterable, Iterator, Literal, NamedTuple, NoReturn, Protocol, TypedDict, overload
|
||||
from typing import TYPE_CHECKING, Any, Iterable, Literal, Protocol, overload
|
||||
|
||||
import jinja2
|
||||
import requests
|
||||
from jinja2.sandbox import ImmutableSandboxedEnvironment
|
||||
from requests.exceptions import ChunkedEncodingError
|
||||
from tqdm import tqdm
|
||||
from urllib3.exceptions import IncompleteRead, ProtocolError
|
||||
|
||||
from ._pyllmodel import (CancellationError as CancellationError, EmbCancelCallbackType, EmbedResult as EmbedResult,
|
||||
LLModel, ResponseCallbackType, _operator_call, empty_response_callback)
|
||||
LLModel, ResponseCallbackType, empty_response_callback)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self, TypeAlias
|
||||
|
||||
if sys.platform == "darwin":
|
||||
if sys.platform == 'darwin':
|
||||
import fcntl
|
||||
|
||||
# TODO: move to config
|
||||
DEFAULT_MODEL_DIRECTORY = Path.home() / ".cache" / "gpt4all"
|
||||
|
||||
ConfigType: TypeAlias = "dict[str, Any]"
|
||||
DEFAULT_PROMPT_TEMPLATE = "### Human:\n{0}\n\n### Assistant:\n"
|
||||
|
||||
# Environment setup adapted from HF transformers
|
||||
@_operator_call
|
||||
def _jinja_env() -> ImmutableSandboxedEnvironment:
|
||||
def raise_exception(message: str) -> NoReturn:
|
||||
raise jinja2.exceptions.TemplateError(message)
|
||||
|
||||
def tojson(obj: Any, indent: int | None = None) -> str:
|
||||
return json.dumps(obj, ensure_ascii=False, indent=indent)
|
||||
|
||||
def strftime_now(fmt: str) -> str:
|
||||
return datetime.now().strftime(fmt)
|
||||
|
||||
env = ImmutableSandboxedEnvironment(trim_blocks=True, lstrip_blocks=True)
|
||||
env.filters["tojson" ] = tojson
|
||||
env.globals["raise_exception"] = raise_exception
|
||||
env.globals["strftime_now" ] = strftime_now
|
||||
return env
|
||||
|
||||
|
||||
class MessageType(TypedDict):
|
||||
role: str
|
||||
content: str
|
||||
|
||||
|
||||
class ChatSession(NamedTuple):
|
||||
template: jinja2.Template
|
||||
history: list[MessageType]
|
||||
ConfigType: TypeAlias = 'dict[str, Any]'
|
||||
MessageType: TypeAlias = 'dict[str, str]'
|
||||
|
||||
|
||||
class Embed4All:
|
||||
@@ -83,7 +55,7 @@ class Embed4All:
|
||||
kwargs: Remaining keyword arguments are passed to the `GPT4All` constructor.
|
||||
"""
|
||||
if model_name is None:
|
||||
model_name = "all-MiniLM-L6-v2.gguf2.f16.gguf"
|
||||
model_name = 'all-MiniLM-L6-v2.gguf2.f16.gguf'
|
||||
self.gpt4all = GPT4All(model_name, n_threads=n_threads, device=device, **kwargs)
|
||||
|
||||
def __enter__(self) -> Self:
|
||||
@@ -174,18 +146,18 @@ class Embed4All:
|
||||
dimensionality = -1
|
||||
else:
|
||||
if dimensionality <= 0:
|
||||
raise ValueError(f"Dimensionality must be None or a positive integer, got {dimensionality}")
|
||||
raise ValueError(f'Dimensionality must be None or a positive integer, got {dimensionality}')
|
||||
if dimensionality < self.MIN_DIMENSIONALITY:
|
||||
warnings.warn(
|
||||
f"Dimensionality {dimensionality} is less than the suggested minimum of {self.MIN_DIMENSIONALITY}."
|
||||
" Performance may be degraded."
|
||||
f'Dimensionality {dimensionality} is less than the suggested minimum of {self.MIN_DIMENSIONALITY}.'
|
||||
' Performance may be degraded.'
|
||||
)
|
||||
try:
|
||||
do_mean = {"mean": True, "truncate": False}[long_text_mode]
|
||||
except KeyError:
|
||||
raise ValueError(f"Long text mode must be one of 'mean' or 'truncate', got {long_text_mode!r}")
|
||||
result = self.gpt4all.model.generate_embeddings(text, prefix, dimensionality, do_mean, atlas, cancel_cb)
|
||||
return result if return_dict else result["embeddings"]
|
||||
return result if return_dict else result['embeddings']
|
||||
|
||||
|
||||
class GPT4All:
|
||||
@@ -233,7 +205,8 @@ class GPT4All:
|
||||
"""
|
||||
|
||||
self.model_type = model_type
|
||||
self._chat_session: ChatSession | None = None
|
||||
self._history: list[MessageType] | None = None
|
||||
self._current_prompt_template: str = "{0}"
|
||||
|
||||
device_init = None
|
||||
if sys.platform == "darwin":
|
||||
@@ -292,13 +265,7 @@ class GPT4All:
|
||||
|
||||
@property
|
||||
def current_chat_session(self) -> list[MessageType] | None:
|
||||
return None if self._chat_session is None else self._chat_session.history
|
||||
|
||||
@current_chat_session.setter
|
||||
def current_chat_session(self, history: list[MessageType]) -> None:
|
||||
if self._chat_session is None:
|
||||
raise ValueError("current_chat_session may only be set when there is an active chat session")
|
||||
self._chat_session.history[:] = history
|
||||
return None if self._history is None else list(self._history)
|
||||
|
||||
@staticmethod
|
||||
def list_models() -> list[ConfigType]:
|
||||
@@ -310,7 +277,7 @@ class GPT4All:
|
||||
"""
|
||||
resp = requests.get("https://gpt4all.io/models/models3.json")
|
||||
if resp.status_code != 200:
|
||||
raise ValueError(f"Request failed: HTTP {resp.status_code} {resp.reason}")
|
||||
raise ValueError(f'Request failed: HTTP {resp.status_code} {resp.reason}')
|
||||
return resp.json()
|
||||
|
||||
@classmethod
|
||||
@@ -340,9 +307,15 @@ class GPT4All:
|
||||
# get the config for the model
|
||||
config: ConfigType = {}
|
||||
if allow_download:
|
||||
models = cls.list_models()
|
||||
if (model := next((m for m in models if m["filename"] == model_filename), None)) is not None:
|
||||
config.update(model)
|
||||
available_models = cls.list_models()
|
||||
|
||||
for m in available_models:
|
||||
if model_filename == m["filename"]:
|
||||
tmpl = m.get("promptTemplate", DEFAULT_PROMPT_TEMPLATE)
|
||||
# change to Python-style formatting
|
||||
m["promptTemplate"] = tmpl.replace("%1", "{0}", 1).replace("%2", "{1}", 1)
|
||||
config.update(m)
|
||||
break
|
||||
|
||||
# Validate download directory
|
||||
if model_path is None:
|
||||
@@ -406,13 +379,13 @@ class GPT4All:
|
||||
headers = {}
|
||||
if offset:
|
||||
print(f"\nDownload interrupted, resuming from byte position {offset}", file=sys.stderr)
|
||||
headers["Range"] = f"bytes={offset}-" # resume incomplete response
|
||||
headers['Range'] = f'bytes={offset}-' # resume incomplete response
|
||||
headers["Accept-Encoding"] = "identity" # Content-Encoding changes meaning of ranges
|
||||
response = requests.get(url, stream=True, headers=headers)
|
||||
if response.status_code not in (200, 206):
|
||||
raise ValueError(f"Request failed: HTTP {response.status_code} {response.reason}")
|
||||
if offset and (response.status_code != 206 or str(offset) not in response.headers.get("Content-Range", "")):
|
||||
raise ValueError("Connection was interrupted and server does not support range requests")
|
||||
raise ValueError(f'Request failed: HTTP {response.status_code} {response.reason}')
|
||||
if offset and (response.status_code != 206 or str(offset) not in response.headers.get('Content-Range', '')):
|
||||
raise ValueError('Connection was interrupted and server does not support range requests')
|
||||
if (enc := response.headers.get("Content-Encoding")) is not None:
|
||||
raise ValueError(f"Expected identity Content-Encoding, got {enc}")
|
||||
return response
|
||||
@@ -511,19 +484,19 @@ class GPT4All:
|
||||
|
||||
def generate(
|
||||
self,
|
||||
prompt : str,
|
||||
prompt: str,
|
||||
*,
|
||||
max_tokens : int = 200,
|
||||
temp : float = 0.7,
|
||||
top_k : int = 40,
|
||||
top_p : float = 0.4,
|
||||
min_p : float = 0.0,
|
||||
repeat_penalty : float = 1.18,
|
||||
repeat_last_n : int = 64,
|
||||
n_batch : int = 8,
|
||||
n_predict : int | None = None,
|
||||
streaming : bool = False,
|
||||
callback : ResponseCallbackType = empty_response_callback,
|
||||
max_tokens: int = 200,
|
||||
temp: float = 0.7,
|
||||
top_k: int = 40,
|
||||
top_p: float = 0.4,
|
||||
min_p: float = 0.0,
|
||||
repeat_penalty: float = 1.18,
|
||||
repeat_last_n: int = 64,
|
||||
n_batch: int = 8,
|
||||
n_predict: int | None = None,
|
||||
streaming: bool = False,
|
||||
callback: ResponseCallbackType = empty_response_callback,
|
||||
) -> Any:
|
||||
"""
|
||||
Generate outputs from any GPT4All model.
|
||||
@@ -548,94 +521,122 @@ class GPT4All:
|
||||
|
||||
# Preparing the model request
|
||||
generate_kwargs: dict[str, Any] = dict(
|
||||
temp = temp,
|
||||
top_k = top_k,
|
||||
top_p = top_p,
|
||||
min_p = min_p,
|
||||
repeat_penalty = repeat_penalty,
|
||||
repeat_last_n = repeat_last_n,
|
||||
n_batch = n_batch,
|
||||
n_predict = n_predict if n_predict is not None else max_tokens,
|
||||
temp=temp,
|
||||
top_k=top_k,
|
||||
top_p=top_p,
|
||||
min_p=min_p,
|
||||
repeat_penalty=repeat_penalty,
|
||||
repeat_last_n=repeat_last_n,
|
||||
n_batch=n_batch,
|
||||
n_predict=n_predict if n_predict is not None else max_tokens,
|
||||
)
|
||||
|
||||
# Prepare the callback, process the model response
|
||||
full_response = ""
|
||||
if self._history is not None:
|
||||
# check if there is only one message, i.e. system prompt:
|
||||
reset = len(self._history) == 1
|
||||
self._history.append({"role": "user", "content": prompt})
|
||||
|
||||
def _callback_wrapper(token_id: int, response: str) -> bool:
|
||||
nonlocal full_response
|
||||
full_response += response
|
||||
return callback(token_id, response)
|
||||
|
||||
last_msg_rendered = prompt
|
||||
if self._chat_session is not None:
|
||||
session = self._chat_session
|
||||
def render(messages: list[MessageType]) -> str:
|
||||
return session.template.render(
|
||||
messages=messages,
|
||||
add_generation_prompt=True,
|
||||
**self.model.special_tokens_map,
|
||||
fct_func = self._format_chat_prompt_template.__func__ # type: ignore[attr-defined]
|
||||
if fct_func is GPT4All._format_chat_prompt_template:
|
||||
if reset:
|
||||
# ingest system prompt
|
||||
# use "%1%2" and not "%1" to avoid implicit whitespace
|
||||
self.model.prompt_model(self._history[0]["content"], "%1%2",
|
||||
empty_response_callback,
|
||||
n_batch=n_batch, n_predict=0, reset_context=True, special=True)
|
||||
prompt_template = self._current_prompt_template.format("%1", "%2")
|
||||
else:
|
||||
warnings.warn(
|
||||
"_format_chat_prompt_template is deprecated. Please use a chat session with a prompt template.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
session.history.append(MessageType(role="user", content=prompt))
|
||||
prompt = render(session.history)
|
||||
if len(session.history) > 1:
|
||||
last_msg_rendered = render(session.history[-1:])
|
||||
# special tokens won't be processed
|
||||
prompt = self._format_chat_prompt_template(
|
||||
self._history[-1:],
|
||||
self._history[0]["content"] if reset else "",
|
||||
)
|
||||
prompt_template = "%1"
|
||||
generate_kwargs["reset_context"] = reset
|
||||
else:
|
||||
prompt_template = "%1"
|
||||
generate_kwargs["reset_context"] = True
|
||||
|
||||
# Check request length
|
||||
last_msg_len = self.model.count_prompt_tokens(last_msg_rendered)
|
||||
if last_msg_len > (limit := self.model.n_ctx - 4):
|
||||
raise ValueError(f"Your message was too long and could not be processed ({last_msg_len} > {limit}).")
|
||||
# Prepare the callback, process the model response
|
||||
output_collector: list[MessageType]
|
||||
output_collector = [
|
||||
{"content": ""}
|
||||
] # placeholder for the self._history if chat session is not activated
|
||||
|
||||
if self._history is not None:
|
||||
self._history.append({"role": "assistant", "content": ""})
|
||||
output_collector = self._history
|
||||
|
||||
def _callback_wrapper(
|
||||
callback: ResponseCallbackType,
|
||||
output_collector: list[MessageType],
|
||||
) -> ResponseCallbackType:
|
||||
def _callback(token_id: int, response: str) -> bool:
|
||||
nonlocal callback, output_collector
|
||||
|
||||
output_collector[-1]["content"] += response
|
||||
|
||||
return callback(token_id, response)
|
||||
|
||||
return _callback
|
||||
|
||||
# Send the request to the model
|
||||
if streaming:
|
||||
def stream() -> Iterator[str]:
|
||||
yield from self.model.prompt_model_streaming(prompt, _callback_wrapper, **generate_kwargs)
|
||||
if self._chat_session is not None:
|
||||
self._chat_session.history.append(MessageType(role="assistant", content=full_response))
|
||||
return stream()
|
||||
return self.model.prompt_model_streaming(
|
||||
prompt,
|
||||
prompt_template,
|
||||
_callback_wrapper(callback, output_collector),
|
||||
**generate_kwargs,
|
||||
)
|
||||
|
||||
self.model.prompt_model(prompt, _callback_wrapper, **generate_kwargs)
|
||||
if self._chat_session is not None:
|
||||
self._chat_session.history.append(MessageType(role="assistant", content=full_response))
|
||||
return full_response
|
||||
self.model.prompt_model(
|
||||
prompt,
|
||||
prompt_template,
|
||||
_callback_wrapper(callback, output_collector),
|
||||
**generate_kwargs,
|
||||
)
|
||||
|
||||
return output_collector[-1]["content"]
|
||||
|
||||
@contextmanager
|
||||
def chat_session(
|
||||
self,
|
||||
system_message: str | Literal[False] | None = None,
|
||||
chat_template: str | None = None,
|
||||
system_prompt: str | None = None,
|
||||
prompt_template: str | None = None,
|
||||
):
|
||||
"""
|
||||
Context manager to hold an inference optimized chat session with a GPT4All model.
|
||||
|
||||
Args:
|
||||
system_message: An initial instruction for the model, None to use the model default, or False to disable. Defaults to None.
|
||||
chat_template: Jinja template for the conversation, or None to use the model default. Defaults to None.
|
||||
system_prompt: An initial instruction for the model.
|
||||
prompt_template: Template for the prompts with {0} being replaced by the user message.
|
||||
"""
|
||||
|
||||
if system_message is None:
|
||||
system_message = self.config.get("systemMessage", False)
|
||||
if system_prompt is None:
|
||||
system_prompt = self.config.get("systemPrompt", "")
|
||||
|
||||
if chat_template is None:
|
||||
if "name" not in self.config:
|
||||
raise ValueError("For sideloaded models or with allow_download=False, you must specify a chat template.")
|
||||
if "chatTemplate" not in self.config:
|
||||
raise NotImplementedError("This model appears to have a built-in chat template, but loading it is not "
|
||||
"currently implemented. Please pass a template to chat_session() directly.")
|
||||
if (tmpl := self.config["chatTemplate"]) is None:
|
||||
raise ValueError(f"The model {self.config['name']!r} does not support chat.")
|
||||
chat_template = tmpl
|
||||
if prompt_template is None:
|
||||
if (tmpl := self.config.get("promptTemplate")) is None:
|
||||
warnings.warn("Use of a sideloaded model or allow_download=False without specifying a prompt template "
|
||||
"is deprecated. Defaulting to Alpaca.", DeprecationWarning)
|
||||
tmpl = DEFAULT_PROMPT_TEMPLATE
|
||||
prompt_template = tmpl
|
||||
|
||||
history = []
|
||||
if system_message is not False:
|
||||
history.append(MessageType(role="system", content=system_message))
|
||||
self._chat_session = ChatSession(
|
||||
template=_jinja_env.from_string(chat_template),
|
||||
history=history,
|
||||
)
|
||||
if re.search(r"%1(?![0-9])", prompt_template):
|
||||
raise ValueError("Prompt template containing a literal '%1' is not supported. For a prompt "
|
||||
"placeholder, please use '{0}' instead.")
|
||||
|
||||
self._history = [{"role": "system", "content": system_prompt}]
|
||||
self._current_prompt_template = prompt_template
|
||||
try:
|
||||
yield self
|
||||
finally:
|
||||
self._chat_session = None
|
||||
self._history = None
|
||||
self._current_prompt_template = "{0}"
|
||||
|
||||
@staticmethod
|
||||
def list_gpus() -> list[str]:
|
||||
@@ -647,6 +648,43 @@ class GPT4All:
|
||||
"""
|
||||
return LLModel.list_gpus()
|
||||
|
||||
def _format_chat_prompt_template(
|
||||
self,
|
||||
messages: list[MessageType],
|
||||
default_prompt_header: str = "",
|
||||
default_prompt_footer: str = "",
|
||||
) -> str:
|
||||
"""
|
||||
Helper method for building a prompt from list of messages using the self._current_prompt_template as a template for each message.
|
||||
|
||||
Warning:
|
||||
This function was deprecated in version 2.3.0, and will be removed in a future release.
|
||||
|
||||
Args:
|
||||
messages: List of dictionaries. Each dictionary should have a "role" key
|
||||
with value of "system", "assistant", or "user" and a "content" key with a
|
||||
string value. Messages are organized such that "system" messages are at top of prompt,
|
||||
and "user" and "assistant" messages are displayed in order. Assistant messages get formatted as
|
||||
"Response: {content}".
|
||||
|
||||
Returns:
|
||||
Formatted prompt.
|
||||
"""
|
||||
|
||||
full_prompt = default_prompt_header + "\n\n" if default_prompt_header != "" else ""
|
||||
|
||||
for message in messages:
|
||||
if message["role"] == "user":
|
||||
user_message = self._current_prompt_template.format(message["content"])
|
||||
full_prompt += user_message
|
||||
if message["role"] == "assistant":
|
||||
assistant_message = message["content"] + "\n"
|
||||
full_prompt += assistant_message
|
||||
|
||||
full_prompt += "\n\n" + default_prompt_footer if default_prompt_footer != "" else ""
|
||||
|
||||
return full_prompt
|
||||
|
||||
|
||||
def append_extension_if_missing(model_name):
|
||||
if not model_name.endswith((".bin", ".gguf")):
|
||||
@@ -659,7 +697,7 @@ class _HasFileno(Protocol):
|
||||
|
||||
|
||||
def _fsync(fd: int | _HasFileno) -> None:
|
||||
if sys.platform == "darwin":
|
||||
if sys.platform == 'darwin':
|
||||
# Apple's fsync does not flush the drive write cache
|
||||
try:
|
||||
fcntl.fcntl(fd, fcntl.F_FULLFSYNC)
|
||||
|
||||
@@ -14,14 +14,10 @@ nav:
|
||||
- 'Models' : 'gpt4all_desktop/models.md'
|
||||
- 'LocalDocs' : 'gpt4all_desktop/localdocs.md'
|
||||
- 'Settings' : 'gpt4all_desktop/settings.md'
|
||||
- 'Chat Templates' : 'gpt4all_desktop/chat_templates.md'
|
||||
- 'Cookbook':
|
||||
- 'Local AI Chat with Microsoft Excel': 'gpt4all_desktop/cookbook/use-local-ai-models-to-privately-chat-with-microsoft-excel.md'
|
||||
- 'Local AI Chat with your Google Drive': 'gpt4all_desktop/cookbook/use-local-ai-models-to-privately-chat-with-google-drive.md'
|
||||
- 'Local AI Chat with your Obsidian Vault': 'gpt4all_desktop/cookbook/use-local-ai-models-to-privately-chat-with-Obsidian.md'
|
||||
- 'Local AI Chat with your OneDrive': 'gpt4all_desktop/cookbook/use-local-ai-models-to-privately-chat-with-One-Drive.md'
|
||||
- 'API Server':
|
||||
- 'gpt4all_api_server/home.md'
|
||||
- 'Python SDK':
|
||||
- 'gpt4all_python/home.md'
|
||||
- 'Monitoring': 'gpt4all_python/monitoring.md'
|
||||
|
||||
@@ -88,10 +88,9 @@ setup(
|
||||
python_requires='>=3.8',
|
||||
packages=find_packages(),
|
||||
install_requires=[
|
||||
'importlib_resources; python_version < "3.9"',
|
||||
'jinja2~=3.1',
|
||||
'requests',
|
||||
'tqdm',
|
||||
'importlib_resources; python_version < "3.9"',
|
||||
'typing-extensions>=4.3.0; python_version >= "3.9" and python_version < "3.11"',
|
||||
],
|
||||
extras_require={
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# vim: set syntax=dosini:
|
||||
[flake8]
|
||||
exclude = .*,__pycache__
|
||||
max-line-length = 120
|
||||
extend-ignore = B001,C408,D,DAR,E221,E303,E722,E741,E800,N801,N806,P101,S101,S324,S404,S406,S410,S603,WPS100,WPS110,WPS111,WPS113,WPS114,WPS115,WPS120,WPS2,WPS300,WPS301,WPS304,WPS305,WPS306,WPS309,WPS316,WPS317,WPS318,WPS319,WPS322,WPS323,WPS326,WPS329,WPS330,WPS332,WPS336,WPS337,WPS347,WPS360,WPS361,WPS407,WPS414,WPS420,WPS421,WPS429,WPS430,WPS431,WPS432,WPS433,WPS437,WPS440,WPS440,WPS441,WPS442,WPS457,WPS458,WPS460,WPS462,WPS463,WPS473,WPS501,WPS504,WPS505,WPS508,WPS509,WPS510,WPS515,WPS516,WPS519,WPS520,WPS529,WPS531,WPS602,WPS604,WPS605,WPS608,WPS609,WPS613,WPS615
|
||||
@@ -4,165 +4,7 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [3.8.0] - 2025-01-30
|
||||
|
||||
### Added
|
||||
- Support DeepSeek-R1 Qwen models ([#3431](https://github.com/nomic-ai/gpt4all/pull/3431))
|
||||
- Support for think tags in the GUI ([#3440](https://github.com/nomic-ai/gpt4all/pull/3440))
|
||||
- Support specifying SHA256 hash in models3.json instead of MD5 ([#3437](https://github.com/nomic-ai/gpt4all/pull/3437))
|
||||
|
||||
### Changed
|
||||
- Use minja instead of Jinja2Cpp for significantly improved template compatibility ([#3433](https://github.com/nomic-ai/gpt4all/pull/3433))
|
||||
|
||||
### Fixed
|
||||
- Fix regression while using localdocs with server API ([#3410](https://github.com/nomic-ai/gpt4all/pull/3410))
|
||||
- Don't show system messages in server chat view ([#3411](https://github.com/nomic-ai/gpt4all/pull/3411))
|
||||
- Fix `codesign --verify` failure on macOS ([#3413](https://github.com/nomic-ai/gpt4all/pull/3413))
|
||||
- Code Interpreter: Fix console.log not accepting a single string after v3.7.0 ([#3426](https://github.com/nomic-ai/gpt4all/pull/3426))
|
||||
- Fix Phi 3.1 Mini 128K Instruct template (by [@ThiloteE](https://github.com/ThiloteE) in [#3412](https://github.com/nomic-ai/gpt4all/pull/3412))
|
||||
- Don't block the gui thread for reasoning ([#3435](https://github.com/nomic-ai/gpt4all/pull/3435))
|
||||
- Fix corruption of unicode in output of reasoning models ([#3443](https://github.com/nomic-ai/gpt4all/pull/3443))
|
||||
|
||||
## [3.7.0] - 2025-01-21
|
||||
|
||||
### Added
|
||||
- Add support for the Windows ARM64 target platform (CPU-only) ([#3385](https://github.com/nomic-ai/gpt4all/pull/3385))
|
||||
|
||||
### Changed
|
||||
- Update from Qt 6.5.1 to 6.8.1 ([#3386](https://github.com/nomic-ai/gpt4all/pull/3386))
|
||||
|
||||
### Fixed
|
||||
- Fix the timeout error in code interpreter ([#3369](https://github.com/nomic-ai/gpt4all/pull/3369))
|
||||
- Fix code interpreter console.log not accepting multiple arguments ([#3371](https://github.com/nomic-ai/gpt4all/pull/3371))
|
||||
- Remove 'X is defined' checks from templates for better compatibility ([#3372](https://github.com/nomic-ai/gpt4all/pull/3372))
|
||||
- Jinja2Cpp: Add 'if' requirement for 'else' parsing to fix crash ([#3373](https://github.com/nomic-ai/gpt4all/pull/3373))
|
||||
- Save chats on quit, even if the window isn't closed first ([#3387](https://github.com/nomic-ai/gpt4all/pull/3387))
|
||||
- Add chat template replacements for five new models and fix EM German Mistral ([#3393](https://github.com/nomic-ai/gpt4all/pull/3393))
|
||||
- Fix crash when entering `{{ a["foo"(` as chat template ([#3394](https://github.com/nomic-ai/gpt4all/pull/3394))
|
||||
- Sign the maintenance tool on macOS to prevent crash on Sequoia ([#3391](https://github.com/nomic-ai/gpt4all/pull/3391))
|
||||
- Jinja2Cpp: Fix operator precedence in 'not X is defined' ([#3402](https://github.com/nomic-ai/gpt4all/pull/3402))
|
||||
|
||||
## [3.6.1] - 2024-12-20
|
||||
|
||||
### Fixed
|
||||
- Fix the stop generation button no longer working in v3.6.0 ([#3336](https://github.com/nomic-ai/gpt4all/pull/3336))
|
||||
- Fix the copy entire conversation button no longer working in v3.6.0 ([#3336](https://github.com/nomic-ai/gpt4all/pull/3336))
|
||||
|
||||
## [3.6.0] - 2024-12-19
|
||||
|
||||
### Added
|
||||
- Automatically substitute chat templates that are not compatible with Jinja2Cpp in GGUFs ([#3327](https://github.com/nomic-ai/gpt4all/pull/3327))
|
||||
- Built-in javascript code interpreter tool plus model ([#3173](https://github.com/nomic-ai/gpt4all/pull/3173))
|
||||
|
||||
### Fixed
|
||||
- Fix remote model template to allow for XML in messages ([#3318](https://github.com/nomic-ai/gpt4all/pull/3318))
|
||||
- Fix Jinja2Cpp bug that broke system message detection in chat templates ([#3325](https://github.com/nomic-ai/gpt4all/pull/3325))
|
||||
- Fix LocalDocs sources displaying in unconsolidated form after v3.5.0 ([#3328](https://github.com/nomic-ai/gpt4all/pull/3328))
|
||||
|
||||
## [3.5.3] - 2024-12-16
|
||||
|
||||
### Fixed
|
||||
- Fix LocalDocs not using information from sources in v3.5.2 ([#3302](https://github.com/nomic-ai/gpt4all/pull/3302))
|
||||
|
||||
## [3.5.2] - 2024-12-13
|
||||
|
||||
### Added
|
||||
- Create separate download pages for built-in and HuggingFace models ([#3269](https://github.com/nomic-ai/gpt4all/pull/3269))
|
||||
|
||||
### Fixed
|
||||
- Fix API server ignoring assistant messages in history after v3.5.0 ([#3256](https://github.com/nomic-ai/gpt4all/pull/3256))
|
||||
- Fix API server replying with incorrect token counts and stop reason after v3.5.0 ([#3256](https://github.com/nomic-ai/gpt4all/pull/3256))
|
||||
- Fix API server remembering previous, unrelated conversations after v3.5.0 ([#3256](https://github.com/nomic-ai/gpt4all/pull/3256))
|
||||
- Fix mishandling of default chat template and system message of cloned models in v3.5.0 ([#3262](https://github.com/nomic-ai/gpt4all/pull/3262))
|
||||
- Fix untranslated text on the startup dialog ([#3293](https://github.com/nomic-ai/gpt4all/pull/3293))
|
||||
|
||||
## [3.5.1] - 2024-12-10
|
||||
|
||||
### Fixed
|
||||
- Fix an incorrect value for currentResponse ([#3245](https://github.com/nomic-ai/gpt4all/pull/3245))
|
||||
- Fix the default model button so it works again after 3.5.0 ([#3246](https://github.com/nomic-ai/gpt4all/pull/3246))
|
||||
- Fix chat templates for Nous Hermes 2 Mistral, Mistral OpenOrca, Qwen 2, and remote models ([#3250](https://github.com/nomic-ai/gpt4all/pull/3250))
|
||||
- Fix chat templates for Llama 3.2 models ([#3251](https://github.com/nomic-ai/gpt4all/pull/3251))
|
||||
|
||||
## [3.5.0] - 2024-12-09
|
||||
|
||||
### Changed
|
||||
- Update Italian translation (by [@Harvester62](https://github.com/Harvester62) in [#3236](https://github.com/nomic-ai/gpt4all/pull/3236))
|
||||
- Update Romanian translation (by [@SINAPSA-IC](https://github.com/SINAPSA-IC) in [#3232](https://github.com/nomic-ai/gpt4all/pull/3232))
|
||||
|
||||
### Fixed
|
||||
- Fix a few more problems with the Jinja changes ([#3239](https://github.com/nomic-ai/gpt4all/pull/3239))
|
||||
|
||||
## [3.5.0-rc2] - 2024-12-06
|
||||
|
||||
### Changed
|
||||
- Fade messages out with an animation when they are removed from the chat view ([#3227](https://github.com/nomic-ai/gpt4all/pull/3227))
|
||||
- Tweak wording of edit/redo confirmation dialogs ([#3228](https://github.com/nomic-ai/gpt4all/pull/3228))
|
||||
- Make edit/redo buttons disabled instead of invisible when they are temporarily unavailable ([#3228](https://github.com/nomic-ai/gpt4all/pull/3228))
|
||||
|
||||
## [3.5.0-rc1] - 2024-12-04
|
||||
|
||||
### Added
|
||||
- Add ability to attach text, markdown, and rst files to chat ([#3135](https://github.com/nomic-ai/gpt4all/pull/3135))
|
||||
- Add feature to minimize to system tray (by [@bgallois](https://github.com/bgallois) in [#3109](https://github.com/nomic-ai/gpt4all/pull/3109))
|
||||
- Basic cache for faster prefill when the input shares a prefix with previous context ([#3073](https://github.com/nomic-ai/gpt4all/pull/3073))
|
||||
- Add ability to edit prompts and regenerate any response ([#3147](https://github.com/nomic-ai/gpt4all/pull/3147))
|
||||
|
||||
### Changed
|
||||
- Implement Qt 6.8 compatibility ([#3121](https://github.com/nomic-ai/gpt4all/pull/3121))
|
||||
- Use Jinja for chat templates instead of per-message QString.arg-style templates ([#3147](https://github.com/nomic-ai/gpt4all/pull/3147))
|
||||
- API server: Use system message(s) from client instead of settings ([#3147](https://github.com/nomic-ai/gpt4all/pull/3147))
|
||||
- API server: Accept messages in any order supported by the model instead of requiring user/assistant pairs ([#3147](https://github.com/nomic-ai/gpt4all/pull/3147))
|
||||
- Remote models: Pass system message with "system" role instead of joining with user message ([#3147](https://github.com/nomic-ai/gpt4all/pull/3147))
|
||||
|
||||
### Removed
|
||||
- Remove option to save binary model state to disk ([#3147](https://github.com/nomic-ai/gpt4all/pull/3147))
|
||||
|
||||
### Fixed
|
||||
- Fix bug in GUI when localdocs encounters binary data ([#3137](https://github.com/nomic-ai/gpt4all/pull/3137))
|
||||
- Fix LocalDocs bugs that prevented some docx files from fully chunking ([#3140](https://github.com/nomic-ai/gpt4all/pull/3140))
|
||||
- Fix missing softmax that was causing crashes and effectively infinite temperature since 3.4.0 ([#3202](https://github.com/nomic-ai/gpt4all/pull/3202))
|
||||
|
||||
## [3.4.2] - 2024-10-16
|
||||
|
||||
### Fixed
|
||||
- Limit bm25 retrieval to only specified collections ([#3083](https://github.com/nomic-ai/gpt4all/pull/3083))
|
||||
- Fix bug removing documents because of a wrong case sensitive file suffix check ([#3083](https://github.com/nomic-ai/gpt4all/pull/3083))
|
||||
- Fix bug with hybrid localdocs search where database would get out of sync ([#3083](https://github.com/nomic-ai/gpt4all/pull/3083))
|
||||
- Fix GUI bug where the localdocs embedding device appears blank ([#3083](https://github.com/nomic-ai/gpt4all/pull/3083))
|
||||
- Prevent LocalDocs from not making progress in certain cases ([#3094](https://github.com/nomic-ai/gpt4all/pull/3094))
|
||||
|
||||
## [3.4.1] - 2024-10-11
|
||||
|
||||
### Fixed
|
||||
- Improve the Italian translation ([#3048](https://github.com/nomic-ai/gpt4all/pull/3048))
|
||||
- Fix models.json cache location ([#3052](https://github.com/nomic-ai/gpt4all/pull/3052))
|
||||
- Fix LocalDocs regressions caused by docx change ([#3079](https://github.com/nomic-ai/gpt4all/pull/3079))
|
||||
- Fix Go code being highlighted as Java ([#3080](https://github.com/nomic-ai/gpt4all/pull/3080))
|
||||
|
||||
## [3.4.0] - 2024-10-08
|
||||
|
||||
### Added
|
||||
- Add bm25 hybrid search to localdocs ([#2969](https://github.com/nomic-ai/gpt4all/pull/2969))
|
||||
- LocalDocs support for .docx files ([#2986](https://github.com/nomic-ai/gpt4all/pull/2986))
|
||||
- Add support for attaching Excel spreadsheet to chat ([#3007](https://github.com/nomic-ai/gpt4all/pull/3007), [#3028](https://github.com/nomic-ai/gpt4all/pull/3028))
|
||||
|
||||
### Changed
|
||||
- Rebase llama.cpp on latest upstream as of September 26th ([#2998](https://github.com/nomic-ai/gpt4all/pull/2998))
|
||||
- Change the error message when a message is too long ([#3004](https://github.com/nomic-ai/gpt4all/pull/3004))
|
||||
- Simplify chatmodel to get rid of unnecessary field and bump chat version ([#3016](https://github.com/nomic-ai/gpt4all/pull/3016))
|
||||
- Allow ChatLLM to have direct access to ChatModel for restoring state from text ([#3018](https://github.com/nomic-ai/gpt4all/pull/3018))
|
||||
- Improvements to XLSX conversion and UI fix ([#3022](https://github.com/nomic-ai/gpt4all/pull/3022))
|
||||
|
||||
### Fixed
|
||||
- Fix a crash when attempting to continue a chat loaded from disk ([#2995](https://github.com/nomic-ai/gpt4all/pull/2995))
|
||||
- Fix the local server rejecting min\_p/top\_p less than 1 ([#2996](https://github.com/nomic-ai/gpt4all/pull/2996))
|
||||
- Fix "regenerate" always forgetting the most recent message ([#3011](https://github.com/nomic-ai/gpt4all/pull/3011))
|
||||
- Fix loaded chats forgetting context when there is a system prompt ([#3015](https://github.com/nomic-ai/gpt4all/pull/3015))
|
||||
- Make it possible to downgrade and keep some chats, and avoid crash for some model types ([#3030](https://github.com/nomic-ai/gpt4all/pull/3030))
|
||||
- Fix scroll positition being reset in model view, and attempt a better fix for the clone issue ([#3042](https://github.com/nomic-ai/gpt4all/pull/3042))
|
||||
|
||||
## [3.3.1] - 2024-09-27 ([v3.3.y](https://github.com/nomic-ai/gpt4all/tree/v3.3.y))
|
||||
## [3.3.1] - 2024-09-26
|
||||
|
||||
### Fixed
|
||||
- Fix a crash when attempting to continue a chat loaded from disk ([#2995](https://github.com/nomic-ai/gpt4all/pull/2995))
|
||||
@@ -283,19 +125,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
- Fix several Vulkan resource management issues ([#2694](https://github.com/nomic-ai/gpt4all/pull/2694))
|
||||
- Fix crash/hang when some models stop generating, by showing special tokens ([#2701](https://github.com/nomic-ai/gpt4all/pull/2701))
|
||||
|
||||
[3.8.0]: https://github.com/nomic-ai/gpt4all/compare/v3.7.0...v3.8.0
|
||||
[3.7.0]: https://github.com/nomic-ai/gpt4all/compare/v3.6.1...v3.7.0
|
||||
[3.6.1]: https://github.com/nomic-ai/gpt4all/compare/v3.6.0...v3.6.1
|
||||
[3.6.0]: https://github.com/nomic-ai/gpt4all/compare/v3.5.3...v3.6.0
|
||||
[3.5.3]: https://github.com/nomic-ai/gpt4all/compare/v3.5.2...v3.5.3
|
||||
[3.5.2]: https://github.com/nomic-ai/gpt4all/compare/v3.5.1...v3.5.2
|
||||
[3.5.1]: https://github.com/nomic-ai/gpt4all/compare/v3.5.0...v3.5.1
|
||||
[3.5.0]: https://github.com/nomic-ai/gpt4all/compare/v3.5.0-rc2...v3.5.0
|
||||
[3.5.0-rc2]: https://github.com/nomic-ai/gpt4all/compare/v3.5.0-rc1...v3.5.0-rc2
|
||||
[3.5.0-rc1]: https://github.com/nomic-ai/gpt4all/compare/v3.4.2...v3.5.0-rc1
|
||||
[3.4.2]: https://github.com/nomic-ai/gpt4all/compare/v3.4.1...v3.4.2
|
||||
[3.4.1]: https://github.com/nomic-ai/gpt4all/compare/v3.4.0...v3.4.1
|
||||
[3.4.0]: https://github.com/nomic-ai/gpt4all/compare/v3.3.0...v3.4.0
|
||||
[3.3.1]: https://github.com/nomic-ai/gpt4all/compare/v3.3.0...v3.3.1
|
||||
[3.3.0]: https://github.com/nomic-ai/gpt4all/compare/v3.2.1...v3.3.0
|
||||
[3.2.1]: https://github.com/nomic-ai/gpt4all/compare/v3.2.0...v3.2.1
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.25) # for try_compile SOURCE_FROM_VAR
|
||||
|
||||
include(../common/common.cmake)
|
||||
|
||||
set(APP_VERSION_MAJOR 3)
|
||||
set(APP_VERSION_MINOR 8)
|
||||
set(APP_VERSION_PATCH 0)
|
||||
set(APP_VERSION_MINOR 3)
|
||||
set(APP_VERSION_PATCH 1)
|
||||
set(APP_VERSION_BASE "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}")
|
||||
set(APP_VERSION "${APP_VERSION_BASE}")
|
||||
|
||||
project(gpt4all VERSION ${APP_VERSION_BASE} LANGUAGES CXX C)
|
||||
|
||||
if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
option(BUILD_UNIVERSAL "Build a Universal binary on macOS" OFF)
|
||||
if(BUILD_UNIVERSAL)
|
||||
@@ -26,34 +20,14 @@ if(APPLE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_package(Python3 3.12 QUIET COMPONENTS Interpreter)
|
||||
|
||||
option(GPT4ALL_TEST "Build the tests" ${Python3_FOUND})
|
||||
option(GPT4ALL_LOCALHOST "Build installer for localhost repo" OFF)
|
||||
option(GPT4ALL_OFFLINE_INSTALLER "Build an offline installer" OFF)
|
||||
option(GPT4ALL_SIGN_INSTALL "Sign installed binaries and installers (requires signing identities)" OFF)
|
||||
option(GPT4ALL_GEN_CPACK_CONFIG "Generate the CPack config.xml in the package step and nothing else." OFF)
|
||||
set(GPT4ALL_USE_QTPDF "AUTO" CACHE STRING "Whether to Use QtPDF for LocalDocs. If OFF or not available on this platform, PDFium is used.")
|
||||
set_property(CACHE GPT4ALL_USE_QTPDF PROPERTY STRINGS AUTO ON OFF)
|
||||
|
||||
include(cmake/cpack_config.cmake)
|
||||
|
||||
if (GPT4ALL_GEN_CPACK_CONFIG)
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/cpack-steal-config.cmake.in"
|
||||
"${CMAKE_BINARY_DIR}/cmake/cpack-steal-config.cmake" @ONLY)
|
||||
set(CPACK_POST_BUILD_SCRIPTS ${CMAKE_BINARY_DIR}/cmake/cpack-steal-config.cmake)
|
||||
include(CPack)
|
||||
include(CPackIFW)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
if (MSVC)
|
||||
# Enable accurate __cplusplus macro
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/Zc:__cplusplus>)
|
||||
endif()
|
||||
|
||||
|
||||
# conftests
|
||||
@@ -96,23 +70,7 @@ configure_file(
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/config.h"
|
||||
)
|
||||
|
||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
|
||||
set(GPT4ALL_QT_COMPONENTS Core HttpServer LinguistTools Quick QuickDialogs2 Sql Svg)
|
||||
set(GPT4ALL_USING_QTPDF OFF)
|
||||
if (CMAKE_SYSTEM_NAME MATCHES Windows AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|AARCH64|arm64|ARM64)$")
|
||||
# QtPDF is not available.
|
||||
if (GPT4ALL_USE_QTPDF STREQUAL "ON")
|
||||
message(FATAL_ERROR "QtPDF is not available on Windows ARM64.")
|
||||
endif()
|
||||
elseif (GPT4ALL_USE_QTPDF MATCHES "^(ON|AUTO)$")
|
||||
set(GPT4ALL_USING_QTPDF ON)
|
||||
list(APPEND GPT4ALL_QT_COMPONENTS Pdf)
|
||||
endif()
|
||||
find_package(Qt6 6.5 COMPONENTS ${GPT4ALL_QT_COMPONENTS} REQUIRED)
|
||||
|
||||
if (QT_KNOWN_POLICY_QTP0004)
|
||||
qt_policy(SET QTP0004 NEW) # generate extra qmldir files on Qt 6.8+
|
||||
endif()
|
||||
find_package(Qt6 6.4 COMPONENTS Core HttpServer LinguistTools Pdf Quick QuickDialogs2 Sql Svg REQUIRED)
|
||||
|
||||
# Get the Qt6Core target properties
|
||||
get_target_property(Qt6Core_INCLUDE_DIRS Qt6::Core INTERFACE_INCLUDE_DIRECTORIES)
|
||||
@@ -128,39 +86,16 @@ get_filename_component(Qt6_ROOT_DIR "${Qt6_ROOT_DIR}/.." ABSOLUTE)
|
||||
message(STATUS "qmake binary: ${QMAKE_EXECUTABLE}")
|
||||
message(STATUS "Qt 6 root directory: ${Qt6_ROOT_DIR}")
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
|
||||
set(FMT_INSTALL OFF)
|
||||
set(BUILD_SHARED_LIBS_SAVED "${BUILD_SHARED_LIBS}")
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
add_subdirectory(deps/fmt)
|
||||
set(BUILD_SHARED_LIBS "${BUILD_SHARED_LIBS_SAVED}")
|
||||
|
||||
add_subdirectory(deps)
|
||||
add_subdirectory(../gpt4all-backend llmodel)
|
||||
|
||||
if (GPT4ALL_TEST)
|
||||
enable_testing()
|
||||
|
||||
# Llama-3.2-1B model
|
||||
set(TEST_MODEL "Llama-3.2-1B-Instruct-Q4_0.gguf")
|
||||
set(TEST_MODEL_MD5 "48ff0243978606fdba19d899b77802fc")
|
||||
set(TEST_MODEL_PATH "${CMAKE_BINARY_DIR}/resources/${TEST_MODEL}")
|
||||
set(TEST_MODEL_URL "https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/${TEST_MODEL}")
|
||||
|
||||
# Create a custom command to download the file if it does not exist or if the checksum does not match
|
||||
add_custom_command(
|
||||
OUTPUT "${TEST_MODEL_PATH}"
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Downloading test model from ${TEST_MODEL_URL} ..."
|
||||
COMMAND ${CMAKE_COMMAND} -DURL="${TEST_MODEL_URL}" -DOUTPUT_PATH="${TEST_MODEL_PATH}" -DEXPECTED_MD5="${TEST_MODEL_MD5}" -P "${CMAKE_SOURCE_DIR}/cmake/download_model.cmake"
|
||||
DEPENDS "${CMAKE_SOURCE_DIR}/cmake/download_model.cmake"
|
||||
)
|
||||
|
||||
# Define a custom target that depends on the downloaded model
|
||||
add_custom_target(download_test_model
|
||||
DEPENDS "${TEST_MODEL_PATH}"
|
||||
)
|
||||
|
||||
add_subdirectory(tests)
|
||||
|
||||
# The 'check' target makes sure the tests and their dependencies are up-to-date before running them
|
||||
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure DEPENDS download_test_model chat gpt4all_tests)
|
||||
endif()
|
||||
|
||||
set(CHAT_EXE_RESOURCES)
|
||||
|
||||
# Metal shader library
|
||||
@@ -198,6 +133,9 @@ if (APPLE)
|
||||
list(APPEND CHAT_EXE_RESOURCES "${LOCAL_EMBEDDING_MODEL_PATH}")
|
||||
endif()
|
||||
|
||||
set(QAPPLICATION_CLASS QGuiApplication)
|
||||
add_subdirectory(deps/SingleApplication)
|
||||
|
||||
if (DEFINED GGML_METALLIB)
|
||||
set_source_files_properties("${GGML_METALLIB}" PROPERTIES GENERATED ON)
|
||||
endif()
|
||||
@@ -205,26 +143,17 @@ if (APPLE)
|
||||
set_source_files_properties(${CHAT_EXE_RESOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
endif()
|
||||
|
||||
set(MACOS_SOURCES)
|
||||
if (APPLE)
|
||||
find_library(COCOA_LIBRARY Cocoa)
|
||||
list(APPEND MACOS_SOURCES src/macosdock.mm src/macosdock.h)
|
||||
endif()
|
||||
|
||||
qt_add_executable(chat
|
||||
src/main.cpp
|
||||
src/chat.cpp src/chat.h
|
||||
src/chatapi.cpp src/chatapi.h
|
||||
src/chatlistmodel.cpp src/chatlistmodel.h
|
||||
src/chatllm.cpp src/chatllm.h
|
||||
src/chatmodel.h src/chatmodel.cpp
|
||||
src/chatmodel.h
|
||||
src/chatviewtextprocessor.cpp src/chatviewtextprocessor.h
|
||||
src/codeinterpreter.cpp src/codeinterpreter.h
|
||||
src/database.cpp src/database.h
|
||||
src/download.cpp src/download.h
|
||||
src/embllm.cpp src/embllm.h
|
||||
src/jinja_helpers.cpp src/jinja_helpers.h
|
||||
src/jinja_replacements.cpp src/jinja_replacements.h
|
||||
src/llm.cpp src/llm.h
|
||||
src/localdocs.cpp src/localdocs.h
|
||||
src/localdocsmodel.cpp src/localdocsmodel.h
|
||||
@@ -233,14 +162,8 @@ qt_add_executable(chat
|
||||
src/mysettings.cpp src/mysettings.h
|
||||
src/network.cpp src/network.h
|
||||
src/server.cpp src/server.h
|
||||
src/tool.cpp src/tool.h
|
||||
src/toolcallparser.cpp src/toolcallparser.h
|
||||
src/toolmodel.cpp src/toolmodel.h
|
||||
src/xlsxtomd.cpp src/xlsxtomd.h
|
||||
${CHAT_EXE_RESOURCES}
|
||||
${MACOS_SOURCES}
|
||||
)
|
||||
gpt4all_add_warning_options(chat)
|
||||
|
||||
qt_add_qml_module(chat
|
||||
URI gpt4all
|
||||
@@ -250,14 +173,8 @@ qt_add_qml_module(chat
|
||||
main.qml
|
||||
qml/AddCollectionView.qml
|
||||
qml/AddModelView.qml
|
||||
qml/AddGPT4AllModelView.qml
|
||||
qml/AddHFModelView.qml
|
||||
qml/ApplicationSettings.qml
|
||||
qml/ChatDrawer.qml
|
||||
qml/ChatCollapsibleItem.qml
|
||||
qml/ChatItemView.qml
|
||||
qml/ChatMessageButton.qml
|
||||
qml/ChatTextItem.qml
|
||||
qml/ChatView.qml
|
||||
qml/CollectionsDrawer.qml
|
||||
qml/HomeView.qml
|
||||
@@ -270,21 +187,17 @@ qt_add_qml_module(chat
|
||||
qml/PopupDialog.qml
|
||||
qml/SettingsView.qml
|
||||
qml/StartupDialog.qml
|
||||
qml/ConfirmationDialog.qml
|
||||
qml/SwitchModelDialog.qml
|
||||
qml/Theme.qml
|
||||
qml/ThumbsDownDialog.qml
|
||||
qml/Toast.qml
|
||||
qml/ToastManager.qml
|
||||
qml/MyBusyIndicator.qml
|
||||
qml/MyButton.qml
|
||||
qml/MyTabButton.qml
|
||||
qml/MyCheckBox.qml
|
||||
qml/MyComboBox.qml
|
||||
qml/MyDialog.qml
|
||||
qml/MyDirectoryField.qml
|
||||
qml/MyFileDialog.qml
|
||||
qml/MyFileIcon.qml
|
||||
qml/MyFolderDialog.qml
|
||||
qml/MyFancyLink.qml
|
||||
qml/MyMenu.qml
|
||||
qml/MyMenuItem.qml
|
||||
@@ -317,12 +230,9 @@ qt_add_qml_module(chat
|
||||
icons/edit.svg
|
||||
icons/eject.svg
|
||||
icons/email.svg
|
||||
icons/file-doc.svg
|
||||
icons/file-docx.svg
|
||||
icons/file-md.svg
|
||||
icons/file-pdf.svg
|
||||
icons/file-txt.svg
|
||||
icons/file-xls.svg
|
||||
icons/file.svg
|
||||
icons/github.svg
|
||||
icons/globe.svg
|
||||
@@ -340,9 +250,7 @@ qt_add_qml_module(chat
|
||||
icons/network.svg
|
||||
icons/nomic_logo.svg
|
||||
icons/notes.svg
|
||||
icons/paperclip.svg
|
||||
icons/plus.svg
|
||||
icons/plus_circle.svg
|
||||
icons/recycle.svg
|
||||
icons/regenerate.svg
|
||||
icons/search.svg
|
||||
@@ -355,7 +263,6 @@ qt_add_qml_module(chat
|
||||
icons/trash.svg
|
||||
icons/twitter.svg
|
||||
icons/up_down.svg
|
||||
icons/webpage.svg
|
||||
icons/you.svg
|
||||
)
|
||||
|
||||
@@ -390,17 +297,15 @@ if (APPLE)
|
||||
OUTPUT_NAME gpt4all
|
||||
)
|
||||
add_dependencies(chat ggml-metal)
|
||||
endif()
|
||||
|
||||
if (APPLE AND GPT4ALL_SIGN_INSTALL)
|
||||
if (NOT MAC_SIGNING_IDENTITY)
|
||||
if (NOT DEFINED ENV{MAC_SIGNING_CERT_NAME})
|
||||
if(NOT MAC_SIGNING_IDENTITY)
|
||||
if(NOT DEFINED ENV{MAC_SIGNING_CERT_NAME} AND GPT4ALL_SIGN_INSTALL)
|
||||
REPORT_MISSING_SIGNING_CONTEXT()
|
||||
endif()
|
||||
set(MAC_SIGNING_IDENTITY $ENV{MAC_SIGNING_CERT_NAME})
|
||||
endif()
|
||||
if (NOT MAC_SIGNING_TID)
|
||||
if (NOT DEFINED ENV{MAC_NOTARIZATION_TID})
|
||||
if(NOT MAC_SIGNING_TID)
|
||||
if(NOT DEFINED ENV{MAC_NOTARIZATION_TID} AND GPT4ALL_SIGN_INSTALL)
|
||||
REPORT_MISSING_SIGNING_CONTEXT()
|
||||
endif()
|
||||
set(MAC_SIGNING_TID $ENV{MAC_NOTARIZATION_TID})
|
||||
@@ -428,38 +333,25 @@ target_include_directories(chat PRIVATE deps/usearch/include
|
||||
deps/usearch/fp16/include)
|
||||
|
||||
target_link_libraries(chat
|
||||
PRIVATE Qt6::Core Qt6::HttpServer Qt6::Quick Qt6::Sql Qt6::Svg)
|
||||
if (GPT4ALL_USING_QTPDF)
|
||||
target_compile_definitions(chat PRIVATE GPT4ALL_USE_QTPDF)
|
||||
target_link_libraries(chat PRIVATE Qt6::Pdf)
|
||||
else()
|
||||
# Link PDFium
|
||||
target_link_libraries(chat PRIVATE pdfium)
|
||||
endif()
|
||||
PRIVATE Qt6::Core Qt6::HttpServer Qt6::Pdf Qt6::Quick Qt6::Sql Qt6::Svg)
|
||||
target_link_libraries(chat
|
||||
PRIVATE llmodel SingleApplication fmt::fmt duckx::duckx QXlsx)
|
||||
target_include_directories(chat PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/deps/json/include)
|
||||
target_include_directories(chat PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/deps/json/include/nlohmann)
|
||||
target_include_directories(chat PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/deps/minja/include)
|
||||
PRIVATE llmodel SingleApplication fmt::fmt)
|
||||
|
||||
if (APPLE)
|
||||
target_link_libraries(chat PRIVATE ${COCOA_LIBRARY})
|
||||
endif()
|
||||
|
||||
# -- install --
|
||||
|
||||
if (APPLE)
|
||||
set(GPT4ALL_LIB_DEST bin/gpt4all.app/Contents/Frameworks)
|
||||
else()
|
||||
set(GPT4ALL_LIB_DEST lib)
|
||||
set(COMPONENT_NAME_MAIN ${PROJECT_NAME})
|
||||
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
install(TARGETS chat DESTINATION bin COMPONENT ${COMPONENT_NAME_MAIN})
|
||||
|
||||
install(
|
||||
TARGETS llmodel
|
||||
LIBRARY DESTINATION ${GPT4ALL_LIB_DEST} COMPONENT ${COMPONENT_NAME_MAIN} # .so/.dylib
|
||||
RUNTIME DESTINATION bin COMPONENT ${COMPONENT_NAME_MAIN} # .dll
|
||||
LIBRARY DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .so/.dylib
|
||||
RUNTIME DESTINATION bin COMPONENT ${COMPONENT_NAME_MAIN} # .dll
|
||||
)
|
||||
|
||||
# We should probably iterate through the list of the cmake for backend, but these need to be installed
|
||||
@@ -482,8 +374,8 @@ endif()
|
||||
|
||||
install(
|
||||
TARGETS ${MODEL_IMPL_TARGETS}
|
||||
LIBRARY DESTINATION ${GPT4ALL_LIB_DEST} COMPONENT ${COMPONENT_NAME_MAIN} # .so/.dylib
|
||||
RUNTIME DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .dll
|
||||
LIBRARY DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .so/.dylib
|
||||
RUNTIME DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .dll
|
||||
)
|
||||
|
||||
if(APPLE AND GPT4ALL_SIGN_INSTALL)
|
||||
@@ -512,7 +404,7 @@ if (LLMODEL_CUDA)
|
||||
TARGETS llamamodel-mainline-cuda
|
||||
llamamodel-mainline-cuda-avxonly
|
||||
RUNTIME_DEPENDENCY_SET llama-cuda-deps
|
||||
LIBRARY DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .so
|
||||
LIBRARY DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .so/.dylib
|
||||
RUNTIME DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .dll
|
||||
)
|
||||
if (WIN32)
|
||||
@@ -526,38 +418,66 @@ if (LLMODEL_CUDA)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT GPT4ALL_USING_QTPDF)
|
||||
# Install PDFium
|
||||
if (WIN32)
|
||||
install(FILES ${PDFium_LIBRARY} DESTINATION bin COMPONENT ${COMPONENT_NAME_MAIN}) # .dll
|
||||
else()
|
||||
install(FILES ${PDFium_LIBRARY} DESTINATION ${GPT4ALL_LIB_DEST} COMPONENT ${COMPONENT_NAME_MAIN}) # .so/.dylib
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT APPLE)
|
||||
install(FILES "${LOCAL_EMBEDDING_MODEL_PATH}"
|
||||
DESTINATION resources
|
||||
COMPONENT ${COMPONENT_NAME_MAIN})
|
||||
endif()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME MATCHES Linux)
|
||||
set(CPACK_GENERATOR "IFW")
|
||||
set(CPACK_VERBATIM_VARIABLES YES)
|
||||
set(CPACK_IFW_VERBOSE ON)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES Linux)
|
||||
find_program(LINUXDEPLOYQT linuxdeployqt HINTS "$ENV{HOME}/dev/linuxdeployqt/build/tools/linuxdeployqt" "$ENV{HOME}/project/linuxdeployqt/bin")
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/deploy-qt-linux.cmake.in"
|
||||
"${CMAKE_BINARY_DIR}/cmake/deploy-qt-linux.cmake" @ONLY)
|
||||
set(CPACK_PRE_BUILD_SCRIPTS ${CMAKE_BINARY_DIR}/cmake/deploy-qt-linux.cmake)
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES Windows)
|
||||
find_program(WINDEPLOYQT windeployqt)
|
||||
set(CPACK_IFW_ROOT "~/Qt/Tools/QtInstallerFramework/4.6")
|
||||
set(CPACK_PACKAGE_FILE_NAME "${COMPONENT_NAME_MAIN}-installer-linux")
|
||||
set(CPACK_IFW_TARGET_DIRECTORY "@HomeDir@/${COMPONENT_NAME_MAIN}")
|
||||
elseif(${CMAKE_SYSTEM_NAME} MATCHES Windows)
|
||||
find_program(WINDEPLOYQT windeployqt HINTS ${_qt_bin_dir})
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/deploy-qt-windows.cmake.in"
|
||||
"${CMAKE_BINARY_DIR}/cmake/deploy-qt-windows.cmake" @ONLY)
|
||||
set(CPACK_PRE_BUILD_SCRIPTS ${CMAKE_BINARY_DIR}/cmake/deploy-qt-windows.cmake)
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES Darwin)
|
||||
find_program(MACDEPLOYQT macdeployqt)
|
||||
set(CPACK_IFW_ROOT "C:/Qt/Tools/QtInstallerFramework/4.6")
|
||||
set(CPACK_IFW_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/resources/gpt4all.ico")
|
||||
set(CPACK_PACKAGE_FILE_NAME "${COMPONENT_NAME_MAIN}-installer-win64")
|
||||
set(CPACK_IFW_TARGET_DIRECTORY "@HomeDir@\\${COMPONENT_NAME_MAIN}")
|
||||
elseif(${CMAKE_SYSTEM_NAME} MATCHES Darwin)
|
||||
find_program(MACDEPLOYQT macdeployqt HINTS ${_qt_bin_dir})
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/deploy-qt-mac.cmake.in"
|
||||
"${CMAKE_BINARY_DIR}/cmake/deploy-qt-mac.cmake" @ONLY)
|
||||
set(CPACK_PRE_BUILD_SCRIPTS ${CMAKE_BINARY_DIR}/cmake/deploy-qt-mac.cmake)
|
||||
set(CPACK_IFW_ROOT "~/Qt/Tools/QtInstallerFramework/4.6")
|
||||
set(CPACK_IFW_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/resources/gpt4all.icns")
|
||||
set(CPACK_PACKAGE_FILE_NAME "${COMPONENT_NAME_MAIN}-installer-darwin")
|
||||
set(CPACK_IFW_TARGET_DIRECTORY "@ApplicationsDir@/${COMPONENT_NAME_MAIN}")
|
||||
set(CPACK_BUNDLE_NAME ${COMPONENT_NAME_MAIN})
|
||||
set(CPACK_BUNDLE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/resources/gpt4all.icns")
|
||||
endif()
|
||||
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${COMPONENT_NAME_MAIN})
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
|
||||
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
|
||||
SET(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
|
||||
set(CPACK_PACKAGE_HOMEPAGE_URL "https://www.nomic.ai/gpt4all")
|
||||
set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/icons/gpt4all-48.png")
|
||||
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE)
|
||||
set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_SOURCE_DIR}/README.md)
|
||||
set(CPACK_PACKAGE_EXECUTABLES "GPT4All")
|
||||
set(CPACK_CREATE_DESKTOP_LINKS "GPT4All")
|
||||
set(CPACK_IFW_PACKAGE_NAME "GPT4All")
|
||||
set(CPACK_IFW_PACKAGE_TITLE "GPT4All Installer")
|
||||
set(CPACK_IFW_PACKAGE_PUBLISHER "Nomic, Inc.")
|
||||
set(CPACK_IFW_PRODUCT_URL "https://www.nomic.ai/gpt4all")
|
||||
set(CPACK_IFW_PACKAGE_WIZARD_STYLE "Aero")
|
||||
set(CPACK_IFW_PACKAGE_LOGO "${CMAKE_CURRENT_SOURCE_DIR}/icons/gpt4all-48.png")
|
||||
set(CPACK_IFW_PACKAGE_WINDOW_ICON "${CMAKE_CURRENT_SOURCE_DIR}/icons/gpt4all-32.png")
|
||||
set(CPACK_IFW_PACKAGE_WIZARD_SHOW_PAGE_LIST OFF)
|
||||
set(CPACK_IFW_PACKAGE_CONTROL_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/cmake/installer_control.qs")
|
||||
|
||||
include(InstallRequiredSystemLibraries)
|
||||
include(CPack)
|
||||
include(CPackIFW)
|
||||
@@ -569,35 +489,20 @@ endif()
|
||||
cpack_ifw_configure_component(${COMPONENT_NAME_MAIN} ESSENTIAL FORCED_INSTALLATION)
|
||||
cpack_ifw_configure_component(${COMPONENT_NAME_MAIN} VERSION ${APP_VERSION})
|
||||
cpack_ifw_configure_component(${COMPONENT_NAME_MAIN} LICENSES "MIT LICENSE" ${CPACK_RESOURCE_FILE_LICENSE})
|
||||
cpack_ifw_configure_component(${COMPONENT_NAME_MAIN} SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/cmake/installer_gpt4all_component.qs")
|
||||
cpack_ifw_configure_component(${COMPONENT_NAME_MAIN} SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/cmake/installer_component.qs")
|
||||
cpack_ifw_configure_component(${COMPONENT_NAME_MAIN} REPLACES "gpt4all-chat") #Was used in very earliest prototypes
|
||||
|
||||
if (APPLE AND GPT4ALL_SIGN_INSTALL)
|
||||
if (GPT4ALL_OFFLINE_INSTALLER)
|
||||
cpack_add_component(maintenancetool HIDDEN)
|
||||
else()
|
||||
cpack_add_component(maintenancetool HIDDEN DOWNLOADED)
|
||||
endif()
|
||||
cpack_ifw_configure_component(maintenancetool ESSENTIAL FORCED_INSTALLATION)
|
||||
cpack_ifw_configure_component(maintenancetool VERSION ${APP_VERSION})
|
||||
cpack_ifw_configure_component(maintenancetool SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/cmake/installer_maintenancetool_component.qs")
|
||||
endif()
|
||||
|
||||
if (GPT4ALL_LOCALHOST)
|
||||
cpack_ifw_add_repository("GPT4AllRepository" URL "http://localhost/repository")
|
||||
elseif (GPT4ALL_OFFLINE_INSTALLER)
|
||||
add_compile_definitions(GPT4ALL_OFFLINE_INSTALLER)
|
||||
elseif(GPT4ALL_OFFLINE_INSTALLER)
|
||||
add_compile_definitions(GPT4ALL_OFFLINE_INSTALLER)
|
||||
else()
|
||||
if (CMAKE_SYSTEM_NAME MATCHES Linux)
|
||||
cpack_ifw_add_repository("GPT4AllRepository" URL "https://gpt4all.io/installer_repos/linux/repository")
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES Windows)
|
||||
# To sign the target on windows have to create a batch script add use it as a custom target and then use CPACK_IFW_EXTRA_TARGETS to set this extra target
|
||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
|
||||
cpack_ifw_add_repository("GPT4AllRepository" URL "https://gpt4all.io/installer_repos/windows/repository")
|
||||
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|AARCH64|arm64|ARM64)$")
|
||||
cpack_ifw_add_repository("GPT4AllRepository" URL "https://gpt4all.io/installer_repos/windows_arm/repository")
|
||||
endif()
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES Darwin)
|
||||
cpack_ifw_add_repository("GPT4AllRepository" URL "https://gpt4all.io/installer_repos/mac/repository")
|
||||
endif()
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES Linux)
|
||||
cpack_ifw_add_repository("GPT4AllRepository" URL "https://gpt4all.io/installer_repos/linux/repository")
|
||||
elseif(${CMAKE_SYSTEM_NAME} MATCHES Windows)
|
||||
#To sign the target on windows have to create a batch script add use it as a custom target and then use CPACK_IFW_EXTRA_TARGETS to set this extra target
|
||||
cpack_ifw_add_repository("GPT4AllRepository" URL "https://gpt4all.io/installer_repos/windows/repository")
|
||||
elseif(${CMAKE_SYSTEM_NAME} MATCHES Darwin)
|
||||
cpack_ifw_add_repository("GPT4AllRepository" URL "https://gpt4all.io/installer_repos/mac/repository")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -21,12 +21,12 @@ sudo pacman -S --needed cmake gcc ninja qt6-5compat qt6-base qt6-declarative qt6
|
||||
|
||||
On Ubuntu 23.04, this looks like:
|
||||
```
|
||||
sudo apt install cmake g++ libgl-dev libqt6core5compat6 ninja-build qml6-module-qt5compat-graphicaleffects qt6-base-private-dev qt6-declarative-dev qt6-httpserver-dev qt6-svg-dev qtcreator
|
||||
sudo apt install cmake g++ libgl-dev libqt6core5compat6 ninja-build qml6-module-qt5compat-graphicaleffects qt6-base-dev qt6-declarative-dev qt6-httpserver-dev qt6-svg-dev qtcreator
|
||||
```
|
||||
|
||||
On Fedora 39, this looks like:
|
||||
```
|
||||
sudo dnf install cmake gcc-c++ ninja-build qt-creator qt5-qtgraphicaleffects qt6-qt5compat qt6-qtbase-private-devel qt6-qtdeclarative-devel qt6-qthttpserver-devel qt6-qtsvg-devel
|
||||
sudo dnf install cmake gcc-c++ ninja-build qt-creator qt5-qtgraphicaleffects qt6-qt5compat qt6-qtbase-devel qt6-qtdeclarative-devel qt6-qthttpserver-devel qt6-qtsvg-devel
|
||||
```
|
||||
|
||||
## Download Qt
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
set(OUTPUT_DIR "@CMAKE_BINARY_DIR@")
|
||||
file(COPY ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/config DESTINATION ${OUTPUT_DIR}/cpack-config)
|
||||
@@ -1,51 +0,0 @@
|
||||
set(COMPONENT_NAME_MAIN "gpt4all")
|
||||
|
||||
set(CPACK_GENERATOR "IFW")
|
||||
set(CPACK_VERBATIM_VARIABLES YES)
|
||||
set(CPACK_IFW_VERBOSE ON)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME MATCHES Linux)
|
||||
set(CPACK_IFW_ROOT "~/Qt/Tools/QtInstallerFramework/4.6")
|
||||
set(CPACK_PACKAGE_FILE_NAME "${COMPONENT_NAME_MAIN}-installer-linux")
|
||||
set(CPACK_IFW_TARGET_DIRECTORY "@HomeDir@/${COMPONENT_NAME_MAIN}")
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES Windows)
|
||||
set(CPACK_IFW_ROOT "C:/Qt/Tools/QtInstallerFramework/4.6")
|
||||
set(CPACK_IFW_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/resources/gpt4all.ico")
|
||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
|
||||
set(CPACK_PACKAGE_FILE_NAME "${COMPONENT_NAME_MAIN}-installer-win64")
|
||||
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|AARCH64|arm64|ARM64)$")
|
||||
set(CPACK_PACKAGE_FILE_NAME "${COMPONENT_NAME_MAIN}-installer-win64-arm")
|
||||
else()
|
||||
message(FATAL_ERROR "Unrecognized processor: ${CMAKE_SYSTEM_PROCESSOR}")
|
||||
endif()
|
||||
set(CPACK_IFW_TARGET_DIRECTORY "@HomeDir@\\${COMPONENT_NAME_MAIN}")
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES Darwin)
|
||||
set(CPACK_IFW_ROOT "~/Qt/Tools/QtInstallerFramework/4.6")
|
||||
set(CPACK_IFW_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/resources/gpt4all.icns")
|
||||
set(CPACK_PACKAGE_FILE_NAME "${COMPONENT_NAME_MAIN}-installer-darwin")
|
||||
set(CPACK_IFW_TARGET_DIRECTORY "@ApplicationsDir@/${COMPONENT_NAME_MAIN}")
|
||||
endif()
|
||||
|
||||
set(CPACK_COMPONENTS_ALL ${COMPONENT_NAME_MAIN}) # exclude development components
|
||||
if (APPLE AND GPT4ALL_SIGN_INSTALL)
|
||||
list(APPEND CPACK_COMPONENTS_ALL maintenancetool)
|
||||
endif()
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${COMPONENT_NAME_MAIN})
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
|
||||
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
|
||||
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
|
||||
set(CPACK_PACKAGE_HOMEPAGE_URL "https://www.nomic.ai/gpt4all")
|
||||
set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/icons/gpt4all-48.png")
|
||||
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE)
|
||||
set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_SOURCE_DIR}/README.md)
|
||||
set(CPACK_PACKAGE_EXECUTABLES "GPT4All")
|
||||
set(CPACK_CREATE_DESKTOP_LINKS "GPT4All")
|
||||
set(CPACK_IFW_PACKAGE_NAME "GPT4All")
|
||||
set(CPACK_IFW_PACKAGE_TITLE "GPT4All Installer")
|
||||
set(CPACK_IFW_PACKAGE_PUBLISHER "Nomic, Inc.")
|
||||
set(CPACK_IFW_PRODUCT_URL "https://www.nomic.ai/gpt4all")
|
||||
set(CPACK_IFW_PACKAGE_WIZARD_STYLE "Aero")
|
||||
set(CPACK_IFW_PACKAGE_LOGO "${CMAKE_CURRENT_SOURCE_DIR}/icons/gpt4all-48.png")
|
||||
set(CPACK_IFW_PACKAGE_WINDOW_ICON "${CMAKE_CURRENT_SOURCE_DIR}/icons/gpt4all-32.png")
|
||||
set(CPACK_IFW_PACKAGE_WIZARD_SHOW_PAGE_LIST OFF)
|
||||
set(CPACK_IFW_PACKAGE_CONTROL_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/cmake/installer_control.qs")
|
||||
@@ -1,26 +1,17 @@
|
||||
set(MACDEPLOYQT "@MACDEPLOYQT@")
|
||||
set(COMPONENT_NAME_MAIN "@COMPONENT_NAME_MAIN@")
|
||||
set(CMAKE_CURRENT_SOURCE_DIR "@CMAKE_CURRENT_SOURCE_DIR@")
|
||||
set(GPT4ALL_SIGN_INSTALL "@GPT4ALL_SIGN_INSTALL@")
|
||||
set(GPT4ALL_SIGNING_ID "@MAC_SIGNING_IDENTITY@")
|
||||
set(CPACK_CONFIG_DIR "@CMAKE_BINARY_DIR@")
|
||||
if (GPT4ALL_SIGN_INSTALL)
|
||||
set(MAC_NOTARIZE -sign-for-notarization=${GPT4ALL_SIGNING_ID})
|
||||
endif()
|
||||
execute_process(COMMAND ${MACDEPLOYQT} ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/bin/gpt4all.app -qmldir=${CMAKE_CURRENT_SOURCE_DIR} -verbose=2 ${MAC_NOTARIZE})
|
||||
execute_process(COMMAND ${MACDEPLOYQT} ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/bin/gpt4all.app -qmldir=${CMAKE_CURRENT_SOURCE_DIR} -verbose=2 -sign-for-notarization=${GPT4ALL_SIGNING_ID})
|
||||
file(GLOB MYLLAMALIBS ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/lib/libllama*)
|
||||
file(GLOB MYLLMODELLIBS ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/lib/libllmodel.*)
|
||||
file(COPY ${MYLLAMALIBS}
|
||||
DESTINATION ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/bin/gpt4all.app/Contents/Frameworks)
|
||||
file(COPY ${MYLLMODELLIBS}
|
||||
DESTINATION ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/bin/gpt4all.app/Contents/Frameworks)
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/icons/gpt4all-32.png"
|
||||
DESTINATION ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data)
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/icons/gpt4all-48.png"
|
||||
DESTINATION ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data)
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/resources/gpt4all.icns"
|
||||
DESTINATION ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data)
|
||||
|
||||
if (GPT4ALL_SIGN_INSTALL)
|
||||
# Create signed MaintenanceTool
|
||||
set(MT_DATA_DIR ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/maintenancetool/data)
|
||||
file(MAKE_DIRECTORY ${MT_DATA_DIR})
|
||||
execute_process(
|
||||
COMMAND binarycreator --config ${CPACK_CONFIG_DIR}/cpack-config/config/config.xml --create-maintenancetool --sign ${GPT4ALL_SIGNING_ID}
|
||||
WORKING_DIRECTORY ${MT_DATA_DIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
if(NOT DEFINED URL OR NOT DEFINED OUTPUT_PATH OR NOT DEFINED EXPECTED_MD5)
|
||||
message(FATAL_ERROR "Usage: cmake -DURL=<url> -DOUTPUT_PATH=<path> -DEXPECTED_MD5=<md5> -P download_model.cmake")
|
||||
endif()
|
||||
|
||||
message(STATUS "Downloading model from ${URL} to ${OUTPUT_PATH} ...")
|
||||
|
||||
file(DOWNLOAD "${URL}" "${OUTPUT_PATH}" EXPECTED_MD5 "${EXPECTED_MD5}" STATUS status)
|
||||
|
||||
list(GET status 0 status_code)
|
||||
if(NOT status_code EQUAL 0)
|
||||
message(FATAL_ERROR "Failed to download model: ${status}")
|
||||
endif()
|
||||
@@ -1,19 +0,0 @@
|
||||
function Component()
|
||||
{
|
||||
component.ifwVersion = installer.value("FrameworkVersion");
|
||||
installer.installationStarted.connect(this, Component.prototype.onInstallationStarted);
|
||||
}
|
||||
|
||||
Component.prototype.onInstallationStarted = function()
|
||||
{
|
||||
if (component.updateRequested() || component.installationRequested()) {
|
||||
if (installer.value("os") == "win") {
|
||||
component.installerbaseBinaryPath = "@TargetDir@/installerbase.exe";
|
||||
} else if (installer.value("os") == "x11") {
|
||||
component.installerbaseBinaryPath = "@TargetDir@/installerbase";
|
||||
} else if (installer.value("os") == "mac") {
|
||||
component.installerbaseBinaryPath = "@TargetDir@/MaintenanceTool.app";
|
||||
}
|
||||
installer.setInstallerBaseBinary(component.installerbaseBinaryPath);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
include(FetchContent)
|
||||
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
|
||||
set(FMT_INSTALL OFF)
|
||||
add_subdirectory(fmt)
|
||||
|
||||
set(QAPPLICATION_CLASS QApplication)
|
||||
add_subdirectory(SingleApplication)
|
||||
|
||||
set(DUCKX_INSTALL OFF)
|
||||
add_subdirectory(DuckX)
|
||||
|
||||
set(QT_VERSION_MAJOR 6)
|
||||
add_subdirectory(QXlsx/QXlsx)
|
||||
|
||||
if (NOT GPT4ALL_USING_QTPDF)
|
||||
# If we do not use QtPDF, we need to get PDFium.
|
||||
set(GPT4ALL_PDFIUM_TAG "chromium/6954")
|
||||
if (CMAKE_SYSTEM_NAME MATCHES Linux)
|
||||
FetchContent_Declare(
|
||||
pdfium
|
||||
URL "https://github.com/bblanchon/pdfium-binaries/releases/download/${GPT4ALL_PDFIUM_TAG}/pdfium-linux-x64.tgz"
|
||||
URL_HASH "SHA256=69917fd9543befc6c806254aff6c8a604d9e7cd3999a3e70fc32b8690d372da2"
|
||||
)
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES Windows)
|
||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
|
||||
FetchContent_Declare(
|
||||
pdfium
|
||||
URL "https://github.com/bblanchon/pdfium-binaries/releases/download/${GPT4ALL_PDFIUM_TAG}/pdfium-win-x64.tgz"
|
||||
URL_HASH "SHA256=62ecac78fbaf658457beaffcc05eb147f493d435a2e1309e6a731808b4e80d38"
|
||||
)
|
||||
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|AARCH64|arm64|ARM64)$")
|
||||
FetchContent_Declare(
|
||||
pdfium
|
||||
URL "https://github.com/bblanchon/pdfium-binaries/releases/download/${GPT4ALL_PDFIUM_TAG}/pdfium-win-arm64.tgz"
|
||||
URL_HASH "SHA256=a0b69014467f2b9824776c064920bc95359c9ba0d88793bdda1894a0f22206f8"
|
||||
)
|
||||
endif()
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES Darwin)
|
||||
FetchContent_Declare(
|
||||
pdfium
|
||||
URL "https://github.com/bblanchon/pdfium-binaries/releases/download/${GPT4ALL_PDFIUM_TAG}/pdfium-mac-univ.tgz"
|
||||
URL_HASH "SHA256=7442f1dc6bef90898b2b7bd38dbec369ddd81bbf66c1c5aac3a1b60e107098f9"
|
||||
)
|
||||
endif()
|
||||
|
||||
FetchContent_MakeAvailable(pdfium)
|
||||
find_package(PDFium REQUIRED PATHS "${pdfium_SOURCE_DIR}" NO_DEFAULT_PATH)
|
||||
endif()
|
||||
@@ -1,11 +0,0 @@
|
||||
-r test-requirements.txt
|
||||
|
||||
# dev tools
|
||||
flake8~=7.1
|
||||
mypy~=1.12
|
||||
pytype>=2024.10.11
|
||||
wemake-python-styleguide~=0.19.2
|
||||
|
||||
# type stubs and other optional modules
|
||||
types-requests~=2.32
|
||||
urllib3[socks]
|
||||
@@ -1 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z"></path></svg>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M28.4138 9.17125L22.8288 3.585C22.643 3.39924 22.4225 3.25188 22.1799 3.15134C21.9372 3.0508 21.6771 2.99905 21.4144 2.99905C21.1517 2.99905 20.8916 3.0508 20.6489 3.15134C20.4062 3.25188 20.1857 3.39924 20 3.585L4.58626 19C4.39973 19.185 4.25185 19.4053 4.15121 19.648C4.05057 19.8907 3.99917 20.151 4.00001 20.4138V26C4.00001 26.5304 4.21072 27.0391 4.5858 27.4142C4.96087 27.7893 5.46958 28 6.00001 28H11.5863C11.849 28.0008 12.1093 27.9494 12.352 27.8488C12.5947 27.7482 12.815 27.6003 13 27.4138L28.4138 12C28.5995 11.8143 28.7469 11.5938 28.8474 11.3511C28.948 11.1084 28.9997 10.8483 28.9997 10.5856C28.9997 10.3229 28.948 10.0628 28.8474 9.82015C28.7469 9.57747 28.5995 9.35698 28.4138 9.17125ZM6.41376 20L17 9.41375L19.0863 11.5L8.50001 22.085L6.41376 20ZM6.00001 22.4138L9.58626 26H6.00001V22.4138ZM12 25.5863L9.91376 23.5L20.5 12.9138L22.5863 15L12 25.5863ZM24 13.5863L18.4138 8L21.4138 5L27 10.585L24 13.5863Z" fill="black"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 372 B After Width: | Height: | Size: 1.0 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M36,152v56H52a28,28,0,0,0,0-56Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M216,200.87A22.12,22.12,0,0,1,200,208c-13.26,0-24-12.54-24-28s10.74-28,24-28a22.12,22.12,0,0,1,16,7.13" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M48,112V40a8,8,0,0,1,8-8h96l56,56v24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><polyline points="152 32 152 88 208 88" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><ellipse cx="128" cy="180" rx="24" ry="28" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
|
||||
|
Before Width: | Height: | Size: 897 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><line x1="152" y1="96" x2="208" y2="96" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="152" y1="160" x2="208" y2="160" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M64,72V40a8,8,0,0,1,8-8H200a8,8,0,0,1,8,8V216a8,8,0,0,1-8,8H72a8,8,0,0,1-8-8V184" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><polyline points="64 104 76 152 92 120 108 152 120 104" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><rect x="32" y="72" width="120" height="112" rx="8" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
|
||||
|
Before Width: | Height: | Size: 893 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><polyline points="148 208 120 208 120 152" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M48,112V40a8,8,0,0,1,8-8h96l56,56v24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><polyline points="152 32 152 88 208 88" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="48" y1="152" x2="88" y2="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="88" y1="152" x2="48" y2="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M203.9,153.6s-29.43-7.78-31.8,11,38.43,10.12,35.78,30.72c-2.47,19.16-31.78,11-31.78,11" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
|
||||
|
Before Width: | Height: | Size: 1019 B |
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 256 256"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="paperclip-horizontal.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="defs10" />
|
||||
<sodipodi:namedview
|
||||
id="namedview8"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.421875"
|
||||
inkscape:cx="127.88693"
|
||||
inkscape:cy="127.88693"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1495"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg6" />
|
||||
<rect
|
||||
width="256"
|
||||
height="256"
|
||||
fill="none"
|
||||
id="rect2" />
|
||||
<path
|
||||
d="m 144,80 v 112 a -16,16 0 0 1 -32,0 V 48 a -32,32 0 0 1 64,0 v 144 a -48,48 0 0 1 -96,0 V 80"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="16"
|
||||
id="path4" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm48-88a8,8,0,0,1-8,8H136v32a8,8,0,0,1-16,0V136H88a8,8,0,0,1,0-16h32V88a8,8,0,0,1,16,0v32h32A8,8,0,0,1,176,128Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 340 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,16V88H40V56Zm0,144H40V104H216v96Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 255 B |
@@ -12,7 +12,6 @@ import network
|
||||
import gpt4all
|
||||
import localdocs
|
||||
import mysettings
|
||||
import Qt.labs.platform
|
||||
|
||||
Window {
|
||||
id: window
|
||||
@@ -23,43 +22,6 @@ Window {
|
||||
visible: true
|
||||
title: qsTr("GPT4All v%1").arg(Qt.application.version)
|
||||
|
||||
SystemTrayIcon {
|
||||
id: systemTrayIcon
|
||||
property bool shouldClose: false
|
||||
visible: MySettings.systemTray && !shouldClose
|
||||
icon.source: "qrc:/gpt4all/icons/gpt4all.svg"
|
||||
|
||||
function restore() {
|
||||
LLM.showDockIcon();
|
||||
window.show();
|
||||
window.raise();
|
||||
window.requestActivate();
|
||||
}
|
||||
onActivated: function(reason) {
|
||||
if (reason === SystemTrayIcon.Context && Qt.platform.os !== "osx")
|
||||
menu.open();
|
||||
else if (reason === SystemTrayIcon.Trigger)
|
||||
restore();
|
||||
}
|
||||
|
||||
menu: Menu {
|
||||
MenuItem {
|
||||
text: qsTr("Restore")
|
||||
onTriggered: systemTrayIcon.restore()
|
||||
}
|
||||
MenuItem {
|
||||
text: qsTr("Quit")
|
||||
onTriggered: {
|
||||
systemTrayIcon.restore();
|
||||
systemTrayIcon.shouldClose = true;
|
||||
window.shouldClose = true;
|
||||
savingPopup.open();
|
||||
ChatListModel.saveChatsForQuit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Settings {
|
||||
property alias x: window.x
|
||||
property alias y: window.y
|
||||
@@ -194,7 +156,7 @@ Window {
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
}
|
||||
|
||||
property bool shouldClose: false
|
||||
property bool hasSaved: false
|
||||
|
||||
PopupDialog {
|
||||
id: savingPopup
|
||||
@@ -218,29 +180,20 @@ Window {
|
||||
}
|
||||
|
||||
onClosing: function(close) {
|
||||
if (systemTrayIcon.visible) {
|
||||
LLM.hideDockIcon();
|
||||
window.visible = false;
|
||||
ChatListModel.saveChats();
|
||||
close.accepted = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.shouldClose)
|
||||
if (window.hasSaved)
|
||||
return;
|
||||
|
||||
window.shouldClose = true;
|
||||
savingPopup.open();
|
||||
ChatListModel.saveChatsForQuit();
|
||||
close.accepted = false;
|
||||
ChatListModel.saveChats();
|
||||
close.accepted = false
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ChatListModel
|
||||
function onSaveChatsFinished() {
|
||||
window.hasSaved = true;
|
||||
savingPopup.close();
|
||||
if (window.shouldClose)
|
||||
window.close()
|
||||
window.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,6 +627,9 @@ Window {
|
||||
|
||||
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() {
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
## Latest News
|
||||
|
||||
GPT4All v3.7.0 was released on January 23rd. Changes include:
|
||||
GPT4All v3.3.0 was released on September 23rd. Changes include:
|
||||
|
||||
* **Windows ARM Support:** GPT4All now supports the Windows ARM platform, ensuring compatibility with devices powered by Qualcomm Snapdragon and Microsoft SQ-series processors.
|
||||
* **NOTE:** Support for GPU and/or NPU acceleration is not available at this time. Only the CPU will be used to run LLMs.
|
||||
* **NOTE:** You must install the new *Windows ARM* version of GPT4All from the website. The standard *Windows* version will not work due to emulation limitations.
|
||||
* **Fixed Updating on macOS:** The maintenance tool no longer crashes when attempting to update or uninstall GPT4All on Sequoia.
|
||||
* **NOTE:** If you have installed the version from the GitHub releases as a workaround for this issue, you can safely uninstall it and switch back to the version from the website.
|
||||
* **Fixed Chat Saving on macOS:** Chats now save as expected when the application is quit with Command-Q.
|
||||
* **Code Interpreter Improvements:**
|
||||
* The behavior when the code takes too long to execute and times out has been improved.
|
||||
* console.log now accepts multiple arguments for better compatibility with native JavaScript.
|
||||
* **Chat Templating Improvements:**
|
||||
* Two crashes and one compatibility issue have been fixed in the chat template parser.
|
||||
* The default chat template for EM German Mistral has been fixed.
|
||||
* Automatic replacements have been added for five new models as we continue to improve compatibility with common chat templates.
|
||||
* **UI Improvements**: The minimum window size now adapts to the font size. A few labels and links have been fixed. The Embeddings Device selection of "Auto"/"Application default" works again. The window icon is now set on Linux. The antenna icon now displays when the API server is listening.
|
||||
* **Single Instance**: Only one instance of GPT4All can be opened at a time. This is now enforced.
|
||||
* **Greedy Sampling**: Set temperature to zero to enable greedy sampling.
|
||||
* **API Server Changes**: The built-in API server now responds correctly to both legacy completions, and chats with message history. Also, it now uses the system prompt configured in the UI.
|
||||
* **Translation Improvements**: The Italian, Romanian, and Traditional Chinese translations have been updated.
|
||||
|
||||
@@ -1,22 +1,6 @@
|
||||
[
|
||||
{
|
||||
"order": "a",
|
||||
"md5sum": "a54c08a7b90e4029a8c2ab5b5dc936aa",
|
||||
"name": "Reasoner v1",
|
||||
"filename": "qwen2.5-coder-7b-instruct-q4_0.gguf",
|
||||
"filesize": "4431390720",
|
||||
"requires": "3.6.0",
|
||||
"ramrequired": "8",
|
||||
"parameters": "8 billion",
|
||||
"quant": "q4_0",
|
||||
"type": "qwen2",
|
||||
"description": "<ul><li>Based on <a href=\"https://huggingface.co/Qwen/Qwen2.5-Coder-7B-Instruct\">Qwen2.5-Coder 7B</a></li><li>Uses built-in javascript code interpreter</li><li>Use for complex reasoning tasks that can be aided by computation analysis</li><li>License: <a href=\"https://huggingface.co/Qwen/Qwen2.5-Coder-7B-Instruct/blob/main/LICENSE\">Apache License Version 2.0</a></li><li>#reasoning</li></ul>",
|
||||
"url": "https://huggingface.co/Qwen/Qwen2.5-Coder-7B-Instruct-GGUF/resolve/main/qwen2.5-coder-7b-instruct-q4_0.gguf",
|
||||
"chatTemplate": "{{- '<|im_start|>system\\n' }}\n{% if toolList|length > 0 %}You have access to the following functions:\n{% for tool in toolList %}\nUse the function '{{tool.function}}' to: '{{tool.description}}'\n{% if tool.parameters|length > 0 %}\nparameters:\n{% for info in tool.parameters %}\n {{info.name}}:\n type: {{info.type}}\n description: {{info.description}}\n required: {{info.required}}\n{% endfor %}\n{% endif %}\n# Tool Instructions\nIf you CHOOSE to call this function ONLY reply with the following format:\n'{{tool.symbolicFormat}}'\nHere is an example. If the user says, '{{tool.examplePrompt}}', then you reply\n'{{tool.exampleCall}}'\nAfter the result you might reply with, '{{tool.exampleReply}}'\n{% endfor %}\nYou MUST include both the start and end tags when you use a function.\n\nYou are a helpful AI assistant who uses the functions to break down, analyze, perform, and verify complex reasoning tasks. You SHOULD try to verify your answers using the functions where possible.\n{% endif %}\n{{- '<|im_end|>\\n' }}\n{% for message in messages %}\n{{'<|im_start|>' + message['role'] + '\\n' + message['content'] + '<|im_end|>\\n' }}\n{% endfor %}\n{% if add_generation_prompt %}\n{{ '<|im_start|>assistant\\n' }}\n{% endif %}\n",
|
||||
"systemPrompt": ""
|
||||
},
|
||||
{
|
||||
"order": "aa",
|
||||
"md5sum": "c87ad09e1e4c8f9c35a5fcef52b6f1c9",
|
||||
"name": "Llama 3 8B Instruct",
|
||||
"filename": "Meta-Llama-3-8B-Instruct.Q4_0.gguf",
|
||||
@@ -29,105 +13,10 @@
|
||||
"description": "<ul><li>Fast responses</li><li>Chat based model</li><li>Accepts system prompts in Llama 3 format</li><li>Trained by Meta</li><li>License: <a href=\"https://llama.meta.com/llama3/license/\">Meta Llama 3 Community License</a></li></ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/Meta-Llama-3-8B-Instruct.Q4_0.gguf",
|
||||
"promptTemplate": "<|start_header_id|>user<|end_header_id|>\n\n%1<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n%2<|eot_id|>",
|
||||
"systemPrompt": "",
|
||||
"chatTemplate": "{%- set loop_messages = messages %}\n{%- for message in loop_messages %}\n {%- set content = '<|start_header_id|>' + message['role'] + '<|end_header_id|>\\n\\n'+ message['content'] | trim + '<|eot_id|>' %}\n {{- content }}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|start_header_id|>assistant<|end_header_id|>\\n\\n' }}\n{%- endif %}"
|
||||
},
|
||||
{
|
||||
"order": "aa1",
|
||||
"sha256sum": "5cd4ee65211770f1d99b4f6f4951780b9ef40e29314bd6542bb5bd0ad0bc29d1",
|
||||
"name": "DeepSeek-R1-Distill-Qwen-7B",
|
||||
"filename": "DeepSeek-R1-Distill-Qwen-7B-Q4_0.gguf",
|
||||
"filesize": "4444121056",
|
||||
"requires": "3.8.0",
|
||||
"ramrequired": "8",
|
||||
"parameters": "7 billion",
|
||||
"quant": "q4_0",
|
||||
"type": "deepseek",
|
||||
"description": "<p>The official Qwen2.5-Math-7B distillation of DeepSeek-R1.</p><ul><li>License: <a href=\"https://opensource.org/license/mit\">MIT</a></li><li>No restrictions on commercial use</li><li>#reasoning</li></ul>",
|
||||
"url": "https://huggingface.co/bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF/resolve/main/DeepSeek-R1-Distill-Qwen-7B-Q4_0.gguf",
|
||||
"chatTemplate": "{%- if not add_generation_prompt is defined %}\n {%- set add_generation_prompt = false %}\n{%- endif %}\n{%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n{%- endif %}\n{%- for message in messages %}\n {%- if message['role'] == 'user' %}\n {{- '<|User|>' + message['content'] }}\n {%- endif %}\n {%- if message['role'] == 'assistant' %}\n {%- set content = message['content'] | regex_replace('^[\\\\s\\\\S]*</think>', '') %}\n {{- '<|Assistant|>' + content + '<|end▁of▁sentence|>' }}\n {%- endif %}\n{%- endfor -%}\n{%- if add_generation_prompt %}\n {{- '<|Assistant|>' }}\n{%- endif %}"
|
||||
},
|
||||
{
|
||||
"order": "aa2",
|
||||
"sha256sum": "906b3382f2680f4ce845459b4a122e904002b075238080307586bcffcde49eef",
|
||||
"name": "DeepSeek-R1-Distill-Qwen-14B",
|
||||
"filename": "DeepSeek-R1-Distill-Qwen-14B-Q4_0.gguf",
|
||||
"filesize": "8544267680",
|
||||
"requires": "3.8.0",
|
||||
"ramrequired": "16",
|
||||
"parameters": "14 billion",
|
||||
"quant": "q4_0",
|
||||
"type": "deepseek",
|
||||
"description": "<p>The official Qwen2.5-14B distillation of DeepSeek-R1.</p><ul><li>License: <a href=\"https://opensource.org/license/mit\">MIT</a></li><li>No restrictions on commercial use</li><li>#reasoning</li></ul>",
|
||||
"url": "https://huggingface.co/bartowski/DeepSeek-R1-Distill-Qwen-14B-GGUF/resolve/main/DeepSeek-R1-Distill-Qwen-14B-Q4_0.gguf",
|
||||
"chatTemplate": "{%- if not add_generation_prompt is defined %}\n {%- set add_generation_prompt = false %}\n{%- endif %}\n{%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n{%- endif %}\n{%- for message in messages %}\n {%- if message['role'] == 'user' %}\n {{- '<|User|>' + message['content'] }}\n {%- endif %}\n {%- if message['role'] == 'assistant' %}\n {%- set content = message['content'] | regex_replace('^[\\\\s\\\\S]*</think>', '') %}\n {{- '<|Assistant|>' + content + '<|end▁of▁sentence|>' }}\n {%- endif %}\n{%- endfor -%}\n{%- if add_generation_prompt %}\n {{- '<|Assistant|>' }}\n{%- endif %}"
|
||||
},
|
||||
{
|
||||
"order": "aa3",
|
||||
"sha256sum": "0eb93e436ac8beec18aceb958c120d282cb2cf5451b23185e7be268fe9d375cc",
|
||||
"name": "DeepSeek-R1-Distill-Llama-8B",
|
||||
"filename": "DeepSeek-R1-Distill-Llama-8B-Q4_0.gguf",
|
||||
"filesize": "4675894112",
|
||||
"requires": "3.8.0",
|
||||
"ramrequired": "8",
|
||||
"parameters": "8 billion",
|
||||
"quant": "q4_0",
|
||||
"type": "deepseek",
|
||||
"description": "<p>The official Llama-3.1-8B distillation of DeepSeek-R1.</p><ul><li>License: <a href=\"https://opensource.org/license/mit\">MIT</a></li><li>No restrictions on commercial use</li><li>#reasoning</li></ul>",
|
||||
"url": "https://huggingface.co/bartowski/DeepSeek-R1-Distill-Llama-8B-GGUF/resolve/main/DeepSeek-R1-Distill-Llama-8B-Q4_0.gguf",
|
||||
"chatTemplate": "{%- if not add_generation_prompt is defined %}\n {%- set add_generation_prompt = false %}\n{%- endif %}\n{%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n{%- endif %}\n{%- for message in messages %}\n {%- if message['role'] == 'user' %}\n {{- '<|User|>' + message['content'] }}\n {%- endif %}\n {%- if message['role'] == 'assistant' %}\n {%- set content = message['content'] | regex_replace('^[\\\\s\\\\S]*</think>', '') %}\n {{- '<|Assistant|>' + content + '<|end▁of▁sentence|>' }}\n {%- endif %}\n{%- endfor -%}\n{%- if add_generation_prompt %}\n {{- '<|Assistant|>' }}\n{%- endif %}"
|
||||
},
|
||||
{
|
||||
"order": "aa4",
|
||||
"sha256sum": "b3af887d0a015b39fab2395e4faf682c1a81a6a3fd09a43f0d4292f7d94bf4d0",
|
||||
"name": "DeepSeek-R1-Distill-Qwen-1.5B",
|
||||
"filename": "DeepSeek-R1-Distill-Qwen-1.5B-Q4_0.gguf",
|
||||
"filesize": "1068807776",
|
||||
"requires": "3.8.0",
|
||||
"ramrequired": "3",
|
||||
"parameters": "1.5 billion",
|
||||
"quant": "q4_0",
|
||||
"type": "deepseek",
|
||||
"description": "<p>The official Qwen2.5-Math-1.5B distillation of DeepSeek-R1.</p><ul><li>License: <a href=\"https://opensource.org/license/mit\">MIT</a></li><li>No restrictions on commercial use</li><li>#reasoning</li></ul>",
|
||||
"url": "https://huggingface.co/bartowski/DeepSeek-R1-Distill-Qwen-1.5B-GGUF/resolve/main/DeepSeek-R1-Distill-Qwen-1.5B-Q4_0.gguf",
|
||||
"chatTemplate": "{%- if not add_generation_prompt is defined %}\n {%- set add_generation_prompt = false %}\n{%- endif %}\n{%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n{%- endif %}\n{%- for message in messages %}\n {%- if message['role'] == 'user' %}\n {{- '<|User|>' + message['content'] }}\n {%- endif %}\n {%- if message['role'] == 'assistant' %}\n {%- set content = message['content'] | regex_replace('^[\\\\s\\\\S]*</think>', '') %}\n {{- '<|Assistant|>' + content + '<|end▁of▁sentence|>' }}\n {%- endif %}\n{%- endfor -%}\n{%- if add_generation_prompt %}\n {{- '<|Assistant|>' }}\n{%- endif %}"
|
||||
"systemPrompt": ""
|
||||
},
|
||||
{
|
||||
"order": "b",
|
||||
"md5sum": "27b44e8ae1817525164ddf4f8dae8af4",
|
||||
"name": "Llama 3.2 3B Instruct",
|
||||
"filename": "Llama-3.2-3B-Instruct-Q4_0.gguf",
|
||||
"filesize": "1921909280",
|
||||
"requires": "3.4.0",
|
||||
"ramrequired": "4",
|
||||
"parameters": "3 billion",
|
||||
"quant": "q4_0",
|
||||
"type": "LLaMA3",
|
||||
"description": "<ul><li>Fast responses</li><li>Instruct model</li><li>Multilingual dialogue use</li><li>Agentic system capable</li><li>Trained by Meta</li><li>License: <a href=\"https://llama.meta.com/llama3_2/license/\">Meta Llama 3.2 Community License</a></li></ul>",
|
||||
"url": "https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF/resolve/main/Llama-3.2-3B-Instruct-Q4_0.gguf",
|
||||
"promptTemplate": "<|start_header_id|>user<|end_header_id|>\n\n%1<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n%2",
|
||||
"systemPrompt": "<|start_header_id|>system<|end_header_id|>\nCutting Knowledge Date: December 2023\n\nYou are a helpful assistant.<|eot_id|>",
|
||||
"chatTemplate": "{{- bos_token }}\n{%- set date_string = strftime_now('%d %b %Y') %}\n\n{#- This block extracts the system message, so we can slot it into the right place. #}\n{%- if messages[0]['role'] == 'system' %}\n {%- set system_message = messages[0]['content'] | trim %}\n {%- set loop_start = 1 %}\n{%- else %}\n {%- set system_message = '' %}\n {%- set loop_start = 0 %}\n{%- endif %}\n\n{#- System message #}\n{{- '<|start_header_id|>system<|end_header_id|>\\n\\n' }}\n{{- 'Cutting Knowledge Date: December 2023\\n' }}\n{{- 'Today Date: ' + date_string + '\\n\\n' }}\n{{- system_message }}\n{{- '<|eot_id|>' }}\n\n{%- for message in messages %}\n {%- if loop.index0 >= loop_start %}\n {{- '<|start_header_id|>' + message['role'] + '<|end_header_id|>\\n\\n' + message['content'] | trim + '<|eot_id|>' }}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|start_header_id|>assistant<|end_header_id|>\\n\\n' }}\n{%- endif %}"
|
||||
},
|
||||
{
|
||||
"order": "c",
|
||||
"md5sum": "48ff0243978606fdba19d899b77802fc",
|
||||
"name": "Llama 3.2 1B Instruct",
|
||||
"filename": "Llama-3.2-1B-Instruct-Q4_0.gguf",
|
||||
"filesize": "773025920",
|
||||
"requires": "3.4.0",
|
||||
"ramrequired": "2",
|
||||
"parameters": "1 billion",
|
||||
"quant": "q4_0",
|
||||
"type": "LLaMA3",
|
||||
"description": "<ul><li>Fast responses</li><li>Instruct model</li><li>Multilingual dialogue use</li><li>Agentic system capable</li><li>Trained by Meta</li><li>License: <a href=\"https://llama.meta.com/llama3_2/license/\">Meta Llama 3.2 Community License</a></li></ul>",
|
||||
"url": "https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/Llama-3.2-1B-Instruct-Q4_0.gguf",
|
||||
"promptTemplate": "<|start_header_id|>user<|end_header_id|>\n\n%1<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n%2",
|
||||
"systemPrompt": "<|start_header_id|>system<|end_header_id|>\nCutting Knowledge Date: December 2023\n\nYou are a helpful assistant.<|eot_id|>",
|
||||
"chatTemplate": "{{- bos_token }}\n{%- set date_string = strftime_now('%d %b %Y') %}\n\n{#- This block extracts the system message, so we can slot it into the right place. #}\n{%- if messages[0]['role'] == 'system' %}\n {%- set system_message = messages[0]['content'] | trim %}\n {%- set loop_start = 1 %}\n{%- else %}\n {%- set system_message = '' %}\n {%- set loop_start = 0 %}\n{%- endif %}\n\n{#- System message #}\n{{- '<|start_header_id|>system<|end_header_id|>\\n\\n' }}\n{{- 'Cutting Knowledge Date: December 2023\\n' }}\n{{- 'Today Date: ' + date_string + '\\n\\n' }}\n{{- system_message }}\n{{- '<|eot_id|>' }}\n\n{%- for message in messages %}\n {%- if loop.index0 >= loop_start %}\n {{- '<|start_header_id|>' + message['role'] + '<|end_header_id|>\\n\\n' + message['content'] | trim + '<|eot_id|>' }}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|start_header_id|>assistant<|end_header_id|>\\n\\n' }}\n{%- endif %}"
|
||||
},
|
||||
{
|
||||
"order": "d",
|
||||
"md5sum": "a5f6b4eabd3992da4d7fb7f020f921eb",
|
||||
"name": "Nous Hermes 2 Mistral DPO",
|
||||
"filename": "Nous-Hermes-2-Mistral-7B-DPO.Q4_0.gguf",
|
||||
@@ -140,11 +29,10 @@
|
||||
"description": "<strong>Good overall fast chat model</strong><br><ul><li>Fast responses</li><li>Chat based model</li><li>Accepts system prompts in ChatML format</li><li>Trained by Mistral AI<li>Finetuned by Nous Research on the OpenHermes-2.5 dataset<li>Licensed for commercial use</ul>",
|
||||
"url": "https://huggingface.co/NousResearch/Nous-Hermes-2-Mistral-7B-DPO-GGUF/resolve/main/Nous-Hermes-2-Mistral-7B-DPO.Q4_0.gguf",
|
||||
"promptTemplate": "<|im_start|>user\n%1<|im_end|>\n<|im_start|>assistant\n%2<|im_end|>\n",
|
||||
"systemPrompt": "",
|
||||
"chatTemplate": "{%- for message in messages %}\n {{- '<|im_start|>' + message['role'] + '\\n' + message['content'] + '<|im_end|>\\n' }}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}"
|
||||
"systemPrompt": ""
|
||||
},
|
||||
{
|
||||
"order": "e",
|
||||
"order": "c",
|
||||
"md5sum": "97463be739b50525df56d33b26b00852",
|
||||
"name": "Mistral Instruct",
|
||||
"filename": "mistral-7b-instruct-v0.1.Q4_0.gguf",
|
||||
@@ -157,11 +45,10 @@
|
||||
"systemPrompt": "",
|
||||
"description": "<strong>Strong overall fast instruction following model</strong><br><ul><li>Fast responses</li><li>Trained by Mistral AI<li>Uncensored</li><li>Licensed for commercial use</li></ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/mistral-7b-instruct-v0.1.Q4_0.gguf",
|
||||
"promptTemplate": "[INST] %1 [/INST]",
|
||||
"chatTemplate": "{%- if messages[0]['role'] == 'system' %}\n {%- set system_message = messages[0]['content'] %}\n {%- set loop_start = 1 %}\n{%- else %}\n {%- set loop_start = 0 %}\n{%- endif %}\n{%- for message in messages %}\n {%- if loop.index0 >= loop_start %}\n {%- if (message['role'] == 'user') != ((loop.index0 - loop_start) % 2 == 0) %}\n {{- raise_exception('After the optional system message, conversation roles must alternate user/assistant/user/assistant/...') }}\n {%- endif %}\n {%- if message['role'] == 'user' %}\n {%- if loop.index0 == loop_start and loop_start == 1 %}\n {{- ' [INST] ' + system_message + '\\n\\n' + message['content'] + ' [/INST]' }}\n {%- else %}\n {{- ' [INST] ' + message['content'] + ' [/INST]' }}\n {%- endif %}\n {%- elif message['role'] == 'assistant' %}\n {{- ' ' + message['content'] + eos_token }}\n {%- else %}\n {{- raise_exception('Only user and assistant roles are supported, with the exception of an initial optional system message!') }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}"
|
||||
"promptTemplate": "[INST] %1 [/INST]"
|
||||
},
|
||||
{
|
||||
"order": "f",
|
||||
"order": "d",
|
||||
"md5sum": "8a9c75bcd8a66b7693f158ec96924eeb",
|
||||
"name": "Llama 3.1 8B Instruct 128k",
|
||||
"filename": "Meta-Llama-3.1-8B-Instruct-128k-Q4_0.gguf",
|
||||
@@ -174,11 +61,10 @@
|
||||
"description": "<ul><li><strong>For advanced users only. Not recommended for use on Windows or Linux without selecting CUDA due to speed issues.</strong></li><li>Fast responses</li><li>Chat based model</li><li>Large context size of 128k</li><li>Accepts agentic system prompts in Llama 3.1 format</li><li>Trained by Meta</li><li>License: <a href=\"https://llama.meta.com/llama3_1/license/\">Meta Llama 3.1 Community License</a></li></ul>",
|
||||
"url": "https://huggingface.co/GPT4All-Community/Meta-Llama-3.1-8B-Instruct-128k/resolve/main/Meta-Llama-3.1-8B-Instruct-128k-Q4_0.gguf",
|
||||
"promptTemplate": "<|start_header_id|>user<|end_header_id|>\n\n%1<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n%2",
|
||||
"systemPrompt": "<|start_header_id|>system<|end_header_id|>\nCutting Knowledge Date: December 2023\n\nYou are a helpful assistant.<|eot_id|>",
|
||||
"chatTemplate": "{%- set loop_messages = messages %}\n{%- for message in loop_messages %}\n {%- set content = '<|start_header_id|>' + message['role'] + '<|end_header_id|>\\n\\n'+ message['content'] | trim + '<|eot_id|>' %}\n {%- if loop.index0 == 0 %}\n {%- set content = bos_token + content %}\n {%- endif %}\n {{- content }}\n{%- endfor %}\n{{- '<|start_header_id|>assistant<|end_header_id|>\\n\\n' }}"
|
||||
"systemPrompt": "<|start_header_id|>system<|end_header_id|>\nCutting Knowledge Date: December 2023\n\nYou are a helpful assistant.<|eot_id|>"
|
||||
},
|
||||
{
|
||||
"order": "g",
|
||||
"order": "e",
|
||||
"md5sum": "f692417a22405d80573ac10cb0cd6c6a",
|
||||
"name": "Mistral OpenOrca",
|
||||
"filename": "mistral-7b-openorca.gguf2.Q4_0.gguf",
|
||||
@@ -191,11 +77,10 @@
|
||||
"description": "<strong>Strong overall fast chat model</strong><br><ul><li>Fast responses</li><li>Chat based model</li><li>Trained by Mistral AI<li>Finetuned on OpenOrca dataset curated via <a href=\"https://atlas.nomic.ai/\">Nomic Atlas</a><li>Licensed for commercial use</ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/mistral-7b-openorca.gguf2.Q4_0.gguf",
|
||||
"promptTemplate": "<|im_start|>user\n%1<|im_end|>\n<|im_start|>assistant\n%2<|im_end|>\n",
|
||||
"systemPrompt": "<|im_start|>system\nYou are MistralOrca, a large language model trained by Alignment Lab AI.\n<|im_end|>\n",
|
||||
"chatTemplate": "{%- for message in messages %}\n {{- '<|im_start|>' + message['role'] + '\\n' + message['content'] + '<|im_end|>\\n' }}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}"
|
||||
"systemPrompt": "<|im_start|>system\nYou are MistralOrca, a large language model trained by Alignment Lab AI.\n<|im_end|>\n"
|
||||
},
|
||||
{
|
||||
"order": "h",
|
||||
"order": "f",
|
||||
"md5sum": "c4c78adf744d6a20f05c8751e3961b84",
|
||||
"name": "GPT4All Falcon",
|
||||
"filename": "gpt4all-falcon-newbpe-q4_0.gguf",
|
||||
@@ -208,11 +93,10 @@
|
||||
"systemPrompt": "",
|
||||
"description": "<strong>Very fast model with good quality</strong><br><ul><li>Fastest responses</li><li>Instruction based</li><li>Trained by TII<li>Finetuned by Nomic AI<li>Licensed for commercial use</ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/gpt4all-falcon-newbpe-q4_0.gguf",
|
||||
"promptTemplate": "### Instruction:\n%1\n\n### Response:\n",
|
||||
"chatTemplate": "{%- if messages[0]['role'] == 'system' %}\n {%- set loop_start = 1 %}\n {{- messages[0]['content'] + '\\n\\n' }}\n{%- else %}\n {%- set loop_start = 0 %}\n{%- endif %}\n{%- for message in messages %}\n {%- if loop.index0 >= loop_start %}\n {%- if message['role'] == 'user' %}\n {{- '### User: ' + message['content'] + '\\n\\n' }}\n {%- elif message['role'] == 'assistant' %}\n {{- '### Assistant: ' + message['content'] + '\\n\\n' }}\n {%- else %}\n {{- raise_exception('Only user and assistant roles are supported, with the exception of an initial optional system message!') }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '### Assistant:' }}\n{%- endif %}"
|
||||
"promptTemplate": "### Instruction:\n%1\n\n### Response:\n"
|
||||
},
|
||||
{
|
||||
"order": "i",
|
||||
"order": "g",
|
||||
"md5sum": "00c8593ba57f5240f59662367b3ed4a5",
|
||||
"name": "Orca 2 (Medium)",
|
||||
"filename": "orca-2-7b.Q4_0.gguf",
|
||||
@@ -224,11 +108,10 @@
|
||||
"type": "LLaMA2",
|
||||
"systemPrompt": "",
|
||||
"description": "<ul><li>Instruction based<li>Trained by Microsoft<li>Cannot be used commercially</ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/orca-2-7b.Q4_0.gguf",
|
||||
"chatTemplate": "{%- for message in messages %}\n {{- '<|im_start|>' + message['role'] + '\\n' + message['content'] + '<|im_end|>\\n' }}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}"
|
||||
"url": "https://gpt4all.io/models/gguf/orca-2-7b.Q4_0.gguf"
|
||||
},
|
||||
{
|
||||
"order": "j",
|
||||
"order": "h",
|
||||
"md5sum": "3c0d63c4689b9af7baa82469a6f51a19",
|
||||
"name": "Orca 2 (Full)",
|
||||
"filename": "orca-2-13b.Q4_0.gguf",
|
||||
@@ -240,11 +123,10 @@
|
||||
"type": "LLaMA2",
|
||||
"systemPrompt": "",
|
||||
"description": "<ul><li>Instruction based<li>Trained by Microsoft<li>Cannot be used commercially</ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/orca-2-13b.Q4_0.gguf",
|
||||
"chatTemplate": "{%- for message in messages %}\n {{- '<|im_start|>' + message['role'] + '\\n' + message['content'] + '<|im_end|>\\n' }}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}"
|
||||
"url": "https://gpt4all.io/models/gguf/orca-2-13b.Q4_0.gguf"
|
||||
},
|
||||
{
|
||||
"order": "k",
|
||||
"order": "i",
|
||||
"md5sum": "5aff90007499bce5c64b1c0760c0b186",
|
||||
"name": "Wizard v1.2",
|
||||
"filename": "wizardlm-13b-v1.2.Q4_0.gguf",
|
||||
@@ -256,12 +138,10 @@
|
||||
"type": "LLaMA2",
|
||||
"systemPrompt": "",
|
||||
"description": "<strong>Strong overall larger model</strong><br><ul><li>Instruction based<li>Gives very long responses<li>Finetuned with only 1k of high-quality data<li>Trained by Microsoft and Peking University<li>Cannot be used commercially</ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/wizardlm-13b-v1.2.Q4_0.gguf",
|
||||
"chatTemplate": "{%- if messages[0]['role'] == 'system' %}\n {%- set loop_start = 1 %}\n {{- messages[0]['content'] + ' ' }}\n{%- else %}\n {%- set loop_start = 0 %}\n{%- endif %}\n{%- for message in loop_messages %}\n {%- if loop.index0 >= loop_start %}\n {%- if message['role'] == 'user' %}\n {{- 'USER: ' + message['content'] }}\n {%- elif message['role'] == 'assistant' %}\n {{- 'ASSISTANT: ' + message['content'] }}\n {%- else %}\n {{- raise_exception('Only user and assistant roles are supported, with the exception of an initial optional system message!') }}\n {%- endif %}\n {%- if (loop.index0 - loop_start) % 2 == 0 %}\n {{- ' ' }}\n {%- else %}\n {{- eos_token }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- 'ASSISTANT:' }}\n{%- endif %}",
|
||||
"systemMessage": "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions."
|
||||
"url": "https://gpt4all.io/models/gguf/wizardlm-13b-v1.2.Q4_0.gguf"
|
||||
},
|
||||
{
|
||||
"order": "l",
|
||||
"order": "j",
|
||||
"md5sum": "31b47b4e8c1816b62684ac3ca373f9e1",
|
||||
"name": "Ghost 7B v0.9.1",
|
||||
"filename": "ghost-7b-v0.9.1-Q4_0.gguf",
|
||||
@@ -274,12 +154,10 @@
|
||||
"description": "<strong>Ghost 7B v0.9.1</strong> fast, powerful and smooth for Vietnamese and English languages.",
|
||||
"url": "https://huggingface.co/lamhieu/ghost-7b-v0.9.1-gguf/resolve/main/ghost-7b-v0.9.1-Q4_0.gguf",
|
||||
"promptTemplate": "<|user|>\n%1</s>\n<|assistant|>\n%2</s>\n",
|
||||
"systemPrompt": "<|system|>\nYou are Ghost created by Lam Hieu. You are a helpful and knowledgeable assistant. You like to help and always give honest information, in its original language. In communication, you are always respectful, equal and promote positive behavior.\n</s>",
|
||||
"chatTemplate": "{%- for message in messages %}\n {%- if message['role'] == 'user' %}\n {{- '<|user|>\\n' + message['content'] + eos_token }}\n {%- elif message['role'] == 'system' %}\n {{- '<|system|>\\n' + message['content'] + eos_token }}\n {%- elif message['role'] == 'assistant' %}\n {{- '<|assistant|>\\n' + message['content'] + eos_token }}\n {%- endif %}\n {%- if loop.last and add_generation_prompt %}\n {{- '<|assistant|>' }}\n {%- endif %}\n{%- endfor %}",
|
||||
"systemMessage": "You are Ghost created by Lam Hieu. You are a helpful and knowledgeable assistant. You like to help and always give honest information, in its original language. In communication, you are always respectful, equal and promote positive behavior."
|
||||
"systemPrompt": "<|system|>\nYou are Ghost created by Lam Hieu. You are a helpful and knowledgeable assistant. You like to help and always give honest information, in its original language. In communication, you are always respectful, equal and promote positive behavior.\n</s>"
|
||||
},
|
||||
{
|
||||
"order": "m",
|
||||
"order": "k",
|
||||
"md5sum": "3d12810391d04d1153b692626c0c6e16",
|
||||
"name": "Hermes",
|
||||
"filename": "nous-hermes-llama2-13b.Q4_0.gguf",
|
||||
@@ -292,11 +170,10 @@
|
||||
"systemPrompt": "",
|
||||
"description": "<strong>Extremely good model</strong><br><ul><li>Instruction based<li>Gives long responses<li>Curated with 300,000 uncensored instructions<li>Trained by Nous Research<li>Cannot be used commercially</ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/nous-hermes-llama2-13b.Q4_0.gguf",
|
||||
"promptTemplate": "### Instruction:\n%1\n\n### Response:\n",
|
||||
"chatTemplate": "{%- if messages[0]['role'] == 'system' %}\n {%- set loop_start = 1 %}\n {{- messages[0]['content'] + '\\n\\n' }}\n{%- else %}\n {%- set loop_start = 0 %}\n{%- endif %}\n{%- for message in messages %}\n {%- if loop.index0 >= loop_start %}\n {%- if message['role'] == 'user' %}\n {{- '### Instruction:\\n' + message['content'] + '\\n\\n' }}\n {%- elif message['role'] == 'assistant' %}\n {{- '### Response:\\n' + message['content'] + '\\n\\n' }}\n {%- else %}\n {{- raise_exception('Only user and assistant roles are supported, with the exception of an initial optional system message!') }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '### Instruction:\\n' }}\n{%- endif %}"
|
||||
"promptTemplate": "### Instruction:\n%1\n\n### Response:\n"
|
||||
},
|
||||
{
|
||||
"order": "n",
|
||||
"order": "l",
|
||||
"md5sum": "40388eb2f8d16bb5d08c96fdfaac6b2c",
|
||||
"name": "Snoozy",
|
||||
"filename": "gpt4all-13b-snoozy-q4_0.gguf",
|
||||
@@ -308,12 +185,10 @@
|
||||
"type": "LLaMA",
|
||||
"systemPrompt": "",
|
||||
"description": "<strong>Very good overall model</strong><br><ul><li>Instruction based<li>Based on the same dataset as Groovy<li>Slower than Groovy, with higher quality responses<li>Trained by Nomic AI<li>Cannot be used commercially</ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/gpt4all-13b-snoozy-q4_0.gguf",
|
||||
"chatTemplate": "{%- if messages[0]['role'] == 'system' %}\n {%- set loop_start = 1 %}\n {{- messages[0]['content'] + '\\n\\n' }}\n{%- else %}\n {%- set loop_start = 0 %}\n{%- endif %}\n{%- for message in messages %}\n {%- if loop.index0 >= loop_start %}\n {%- if message['role'] == 'user' %}\n {{- '### Instruction:\\n' + message['content'] + '\\n\\n' }}\n {%- elif message['role'] == 'assistant' %}\n {{- '### Response:\\n' + message['content'] + '\\n\\n' }}\n {%- else %}\n {{- raise_exception('Only user and assistant roles are supported, with the exception of an initial optional system message!') }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '### Response:\\n' }}\n{%- endif %}",
|
||||
"systemMessage": "Below is an instruction that describes a task. Write a response that appropriately completes the request."
|
||||
"url": "https://gpt4all.io/models/gguf/gpt4all-13b-snoozy-q4_0.gguf"
|
||||
},
|
||||
{
|
||||
"order": "o",
|
||||
"order": "m",
|
||||
"md5sum": "15dcb4d7ea6de322756449c11a0b7545",
|
||||
"name": "MPT Chat",
|
||||
"filename": "mpt-7b-chat-newbpe-q4_0.gguf",
|
||||
@@ -327,11 +202,10 @@
|
||||
"description": "<strong>Good model with novel architecture</strong><br><ul><li>Fast responses<li>Chat based<li>Trained by Mosaic ML<li>Cannot be used commercially</ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/mpt-7b-chat-newbpe-q4_0.gguf",
|
||||
"promptTemplate": "<|im_start|>user\n%1<|im_end|>\n<|im_start|>assistant\n%2<|im_end|>\n",
|
||||
"systemPrompt": "<|im_start|>system\n- You are a helpful assistant chatbot trained by MosaicML.\n- You answer questions.\n- You are excited to be able to help the user, but will refuse to do anything that could be considered harmful to the user.\n- You are more than just an information source, you are also able to write poetry, short stories, and make jokes.<|im_end|>\n",
|
||||
"chatTemplate": "{%- for message in messages %}\n {{- '<|im_start|>' + message['role'] + '\\n' + message['content'] + '<|im_end|>\\n' }}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}"
|
||||
"systemPrompt": "<|im_start|>system\n- You are a helpful assistant chatbot trained by MosaicML.\n- You answer questions.\n- You are excited to be able to help the user, but will refuse to do anything that could be considered harmful to the user.\n- You are more than just an information source, you are also able to write poetry, short stories, and make jokes.<|im_end|>\n"
|
||||
},
|
||||
{
|
||||
"order": "p",
|
||||
"order": "n",
|
||||
"md5sum": "ab5d8e8a2f79365ea803c1f1d0aa749d",
|
||||
"name": "MPT Chat",
|
||||
"filename": "mpt-7b-chat.gguf4.Q4_0.gguf",
|
||||
@@ -344,11 +218,10 @@
|
||||
"description": "<strong>Good model with novel architecture</strong><br><ul><li>Fast responses<li>Chat based<li>Trained by Mosaic ML<li>Cannot be used commercially</ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/mpt-7b-chat.gguf4.Q4_0.gguf",
|
||||
"promptTemplate": "<|im_start|>user\n%1<|im_end|>\n<|im_start|>assistant\n%2<|im_end|>\n",
|
||||
"systemPrompt": "<|im_start|>system\n- You are a helpful assistant chatbot trained by MosaicML.\n- You answer questions.\n- You are excited to be able to help the user, but will refuse to do anything that could be considered harmful to the user.\n- You are more than just an information source, you are also able to write poetry, short stories, and make jokes.<|im_end|>\n",
|
||||
"chatTemplate": "{%- for message in messages %}\n {{- '<|im_start|>' + message['role'] + '\\n' + message['content'] + '<|im_end|>\\n' }}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}"
|
||||
"systemPrompt": "<|im_start|>system\n- You are a helpful assistant chatbot trained by MosaicML.\n- You answer questions.\n- You are excited to be able to help the user, but will refuse to do anything that could be considered harmful to the user.\n- You are more than just an information source, you are also able to write poetry, short stories, and make jokes.<|im_end|>\n"
|
||||
},
|
||||
{
|
||||
"order": "q",
|
||||
"order": "o",
|
||||
"md5sum": "f8347badde9bfc2efbe89124d78ddaf5",
|
||||
"name": "Phi-3 Mini Instruct",
|
||||
"filename": "Phi-3-mini-4k-instruct.Q4_0.gguf",
|
||||
@@ -361,11 +234,10 @@
|
||||
"description": "<ul><li>Very fast responses</li><li>Chat based model</li><li>Accepts system prompts in Phi-3 format</li><li>Trained by Microsoft</li><li>License: <a href=\"https://opensource.org/license/mit\">MIT</a></li><li>No restrictions on commercial use</li></ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/Phi-3-mini-4k-instruct.Q4_0.gguf",
|
||||
"promptTemplate": "<|user|>\n%1<|end|>\n<|assistant|>\n%2<|end|>\n",
|
||||
"systemPrompt": "",
|
||||
"chatTemplate": "{{- bos_token }}\n{%- for message in messages %}\n {{- '<|' + message['role'] + '|>\\n' + message['content'] + '<|end|>\\n' }}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|assistant|>\\n' }}\n{%- else %}\n {{- eos_token }}\n{%- endif %}"
|
||||
"systemPrompt": ""
|
||||
},
|
||||
{
|
||||
"order": "r",
|
||||
"order": "p",
|
||||
"md5sum": "0e769317b90ac30d6e09486d61fefa26",
|
||||
"name": "Mini Orca (Small)",
|
||||
"filename": "orca-mini-3b-gguf2-q4_0.gguf",
|
||||
@@ -378,11 +250,10 @@
|
||||
"description": "<strong>Small version of new model with novel dataset</strong><br><ul><li>Very fast responses</li><li>Instruction based</li><li>Explain tuned datasets</li><li>Orca Research Paper dataset construction approaches</li><li>Cannot be used commercially</li></ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/orca-mini-3b-gguf2-q4_0.gguf",
|
||||
"promptTemplate": "### User:\n%1\n\n### Response:\n",
|
||||
"systemPrompt": "### System:\nYou are an AI assistant that follows instruction extremely well. Help as much as you can.\n\n",
|
||||
"chatTemplate": "{%- if messages[0]['role'] == 'system' %}\n {%- set loop_start = 1 %}\n {{- '### System:\\n' + messages[0]['content'] + '\\n\\n' }}\n{%- else %}\n {%- set loop_start = 0 %}\n{%- endif %}\n{%- for message in messages %}\n {%- if loop.index0 >= loop_start %}\n {%- if message['role'] == 'user' %}\n {{- '### User:\\n' + message['content'] + '\\n\\n' }}\n {%- elif message['role'] == 'assistant' %}\n {{- '### Response:\\n' + message['content'] + '\\n\\n' }}\n {%- else %}\n {{- raise_exception('Only user and assistant roles are supported, with the exception of an initial optional system message!') }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '### Response:\\n' }}\n{%- endif %}"
|
||||
"systemPrompt": "### System:\nYou are an AI assistant that follows instruction extremely well. Help as much as you can.\n\n"
|
||||
},
|
||||
{
|
||||
"order": "s",
|
||||
"order": "q",
|
||||
"md5sum": "c232f17e09bca4b7ee0b5b1f4107c01e",
|
||||
"disableGUI": "true",
|
||||
"name": "Replit",
|
||||
@@ -396,11 +267,10 @@
|
||||
"systemPrompt": "",
|
||||
"promptTemplate": "%1",
|
||||
"description": "<strong>Trained on subset of the Stack</strong><br><ul><li>Code completion based<li>Licensed for commercial use<li>WARNING: Not available for chat GUI</ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/replit-code-v1_5-3b-newbpe-q4_0.gguf",
|
||||
"chatTemplate": null
|
||||
"url": "https://gpt4all.io/models/gguf/replit-code-v1_5-3b-newbpe-q4_0.gguf"
|
||||
},
|
||||
{
|
||||
"order": "t",
|
||||
"order": "r",
|
||||
"md5sum": "70841751ccd95526d3dcfa829e11cd4c",
|
||||
"disableGUI": "true",
|
||||
"name": "Starcoder",
|
||||
@@ -414,11 +284,10 @@
|
||||
"systemPrompt": "",
|
||||
"promptTemplate": "%1",
|
||||
"description": "<strong>Trained on subset of the Stack</strong><br><ul><li>Code completion based<li>WARNING: Not available for chat GUI</ul>",
|
||||
"url": "https://gpt4all.io/models/gguf/starcoder-newbpe-q4_0.gguf",
|
||||
"chatTemplate": null
|
||||
"url": "https://gpt4all.io/models/gguf/starcoder-newbpe-q4_0.gguf"
|
||||
},
|
||||
{
|
||||
"order": "u",
|
||||
"order": "s",
|
||||
"md5sum": "e973dd26f0ffa6e46783feaea8f08c83",
|
||||
"disableGUI": "true",
|
||||
"name": "Rift coder",
|
||||
@@ -432,11 +301,10 @@
|
||||
"systemPrompt": "",
|
||||
"promptTemplate": "%1",
|
||||
"description": "<strong>Trained on collection of Python and TypeScript</strong><br><ul><li>Code completion based<li>WARNING: Not available for chat GUI</li>",
|
||||
"url": "https://gpt4all.io/models/gguf/rift-coder-v0-7b-q4_0.gguf",
|
||||
"chatTemplate": null
|
||||
"url": "https://gpt4all.io/models/gguf/rift-coder-v0-7b-q4_0.gguf"
|
||||
},
|
||||
{
|
||||
"order": "v",
|
||||
"order": "t",
|
||||
"md5sum": "e479e6f38b59afc51a470d1953a6bfc7",
|
||||
"disableGUI": "true",
|
||||
"name": "SBert",
|
||||
@@ -451,11 +319,10 @@
|
||||
"embeddingModel": true,
|
||||
"systemPrompt": "",
|
||||
"description": "<strong>LocalDocs text embeddings model</strong><br><ul><li>For use with LocalDocs feature<li>Used for retrieval augmented generation (RAG)",
|
||||
"url": "https://gpt4all.io/models/gguf/all-MiniLM-L6-v2-f16.gguf",
|
||||
"chatTemplate": null
|
||||
"url": "https://gpt4all.io/models/gguf/all-MiniLM-L6-v2-f16.gguf"
|
||||
},
|
||||
{
|
||||
"order": "w",
|
||||
"order": "u",
|
||||
"md5sum": "dd90e2cb7f8e9316ac3796cece9883b5",
|
||||
"name": "SBert",
|
||||
"filename": "all-MiniLM-L6-v2.gguf2.f16.gguf",
|
||||
@@ -468,11 +335,10 @@
|
||||
"type": "Bert",
|
||||
"embeddingModel": true,
|
||||
"description": "<strong>LocalDocs text embeddings model</strong><br><ul><li>For use with LocalDocs feature<li>Used for retrieval augmented generation (RAG)",
|
||||
"url": "https://gpt4all.io/models/gguf/all-MiniLM-L6-v2.gguf2.f16.gguf",
|
||||
"chatTemplate": null
|
||||
"url": "https://gpt4all.io/models/gguf/all-MiniLM-L6-v2.gguf2.f16.gguf"
|
||||
},
|
||||
{
|
||||
"order": "x",
|
||||
"order": "v",
|
||||
"md5sum": "919de4dd6f25351bcb0223790db1932d",
|
||||
"name": "EM German Mistral",
|
||||
"filename": "em_german_mistral_v01.Q4_0.gguf",
|
||||
@@ -485,12 +351,10 @@
|
||||
"description": "<strong>Mistral-based model for German-language applications</strong><br><ul><li>Fast responses</li><li>Chat based model</li><li>Trained by ellamind<li>Finetuned on German instruction and chat data</a><li>Licensed for commercial use</ul>",
|
||||
"url": "https://huggingface.co/TheBloke/em_german_mistral_v01-GGUF/resolve/main/em_german_mistral_v01.Q4_0.gguf",
|
||||
"promptTemplate": "USER: %1 ASSISTANT: ",
|
||||
"systemPrompt": "Du bist ein hilfreicher Assistent. ",
|
||||
"chatTemplate": "{%- if messages[0]['role'] == 'system' %}\n {%- set loop_start = 1 %}\n {{- messages[0]['content'] }}\n{%- else %}\n {%- set loop_start = 0 %}\n{%- endif %}\n{%- for message in messages %}\n {%- if loop.index0 >= loop_start %}\n {%- if not loop.first %}\n {{- ' ' }}\n {%- endif %}\n {%- if message['role'] == 'user' %}\n {{- 'USER: ' + message['content'] }}\n {%- elif message['role'] == 'assistant' %}\n {{- 'ASSISTANT: ' + message['content'] }}\n {%- else %}\n {{- raise_exception('After the optional system message, conversation roles must be either user or assistant.') }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {%- if messages %}\n {{- ' ' }}\n {%- endif %}\n {{- 'ASSISTANT:' }}\n{%- endif %}",
|
||||
"systemMessage": "Du bist ein hilfreicher Assistent."
|
||||
"systemPrompt": "Du bist ein hilfreicher Assistent. "
|
||||
},
|
||||
{
|
||||
"order": "y",
|
||||
"order": "w",
|
||||
"md5sum": "60ea031126f82db8ddbbfecc668315d2",
|
||||
"disableGUI": "true",
|
||||
"name": "Nomic Embed Text v1",
|
||||
@@ -504,11 +368,10 @@
|
||||
"embeddingModel": true,
|
||||
"systemPrompt": "",
|
||||
"description": "nomic-embed-text-v1",
|
||||
"url": "https://gpt4all.io/models/gguf/nomic-embed-text-v1.f16.gguf",
|
||||
"chatTemplate": null
|
||||
"url": "https://gpt4all.io/models/gguf/nomic-embed-text-v1.f16.gguf"
|
||||
},
|
||||
{
|
||||
"order": "z",
|
||||
"order": "x",
|
||||
"md5sum": "a5401e7f7e46ed9fcaed5b60a281d547",
|
||||
"disableGUI": "true",
|
||||
"name": "Nomic Embed Text v1.5",
|
||||
@@ -522,24 +385,22 @@
|
||||
"embeddingModel": true,
|
||||
"systemPrompt": "",
|
||||
"description": "nomic-embed-text-v1.5",
|
||||
"url": "https://gpt4all.io/models/gguf/nomic-embed-text-v1.5.f16.gguf",
|
||||
"chatTemplate": null
|
||||
"url": "https://gpt4all.io/models/gguf/nomic-embed-text-v1.5.f16.gguf"
|
||||
},
|
||||
{
|
||||
"order": "zzz",
|
||||
"order": "z",
|
||||
"md5sum": "a8c5a783105f87a481543d4ed7d7586d",
|
||||
"name": "Qwen2-1.5B-Instruct",
|
||||
"filename": "qwen2-1_5b-instruct-q4_0.gguf",
|
||||
"filesize": "937532800",
|
||||
"requires": "3.0",
|
||||
"ramrequired": "3",
|
||||
"ramrequired": "4",
|
||||
"parameters": "1.5 billion",
|
||||
"quant": "q4_0",
|
||||
"type": "qwen2",
|
||||
"description": "<ul><li>Very fast responses</li><li>Instruction based model</li><li>Usage of LocalDocs (RAG): Highly recommended</li><li>Supports context length of up to 32768</li><li>Trained and finetuned by Qwen (Alibaba Cloud)</li><li>License: <a href=\"https://www.apache.org/licenses/LICENSE-2.0.html/\">Apache 2.0</a></li></ul>",
|
||||
"url": "https://huggingface.co/Qwen/Qwen2-1.5B-Instruct-GGUF/resolve/main/qwen2-1_5b-instruct-q4_0.gguf",
|
||||
"promptTemplate": "<|im_start|>user\n%1<|im_end|>\n<|im_start|>assistant\n%2<|im_end|>",
|
||||
"systemPrompt": "<|im_start|>system\nBelow is an instruction that describes a task. Write a response that appropriately completes the request.<|im_end|>\n",
|
||||
"chatTemplate": "{%- for message in messages %}\n {%- if loop.first and messages[0]['role'] != 'system' %}\n {{- '<|im_start|>system\\nYou are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n {{- '<|im_start|>' + message['role'] + '\\n' + message['content'] + '<|im_end|>\\n' }}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}"
|
||||
"systemPrompt": "<|im_start|>system\nBelow is an instruction that describes a task. Write a response that appropriately completes the request.<|im_end|>\n"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -208,60 +208,5 @@
|
||||
"version": "3.3.0",
|
||||
"notes": "* **UI Improvements**: The minimum window size now adapts to the font size. A few labels and links have been fixed. The Embeddings Device selection of \"Auto\"/\"Application default\" works again. The window icon is now set on Linux. The antenna icon now displays when the API server is listening.\n* **Single Instance**: Only one instance of GPT4All can be opened at a time. This is now enforced.\n* **Greedy Sampling**: Set temperature to zero to enable greedy sampling.\n* **API Server Changes**: The built-in API server now responds correctly to both legacy completions, and chats with message history. Also, it now uses the system prompt configured in the UI.\n* **Translation Improvements**: The Italian, Romanian, and Traditional Chinese translations have been updated.\n",
|
||||
"contributors": "* Jared Van Bortel (Nomic AI)\n* Adam Treat (Nomic AI)\n* 3Simplex (`@3Simplex`)\n* Riccardo Giovanetti (`@Harvester62`)\n* Victor Emanuel (`@SINAPSA-IC`)\n* Dominik (`@cosmic-snow`)\n* Shiranui (`@supersonictw`)"
|
||||
},
|
||||
{
|
||||
"version": "3.3.1",
|
||||
"notes": "* Fixed a crash when attempting to continue a chat loaded from disk\n* Fixed the local server rejecting min\\_p/top\\_p less than 1\n",
|
||||
"contributors": "* Jared Van Bortel (Nomic AI)"
|
||||
},
|
||||
{
|
||||
"version": "3.4.0",
|
||||
"notes": "* **Attached Files:** You can now attach a small Microsoft Excel spreadsheet (.xlsx) to a chat message and ask the model about it.\n* **LocalDocs Accuracy:** The LocalDocs algorithm has been enhanced to find more accurate references for some queries.\n* **Word Document Support:** LocalDocs now supports Microsoft Word (.docx) documents natively.\n * **IMPORTANT NOTE:** If .docx files are not found, make sure Settings > LocalDocs > Allowed File Extensions includes \"docx\".\n* **Forgetful Model Fixes:** Issues with the \"Redo last chat response\" button, and with continuing chats from previous sessions, have been fixed.\n* **Chat Saving Improvements:** On exit, GPT4All will no longer save chats that are not new or modified. As a bonus, downgrading without losing access to all chats will be possible in the future, should the need arise.\n* **UI Fixes:** The model list no longer scrolls to the top when you start downloading a model.\n* **New Models:** LLama 3.2 Instruct 3B and 1B models now available in model list.\n",
|
||||
"contributors": "* Jared Van Bortel (Nomic AI)\n* Adam Treat (Nomic AI)\n* Andriy Mulyar (Nomic AI)\n* Ikko Eltociear Ashimine (`@eltociear`)\n* Victor Emanuel (`@SINAPSA-IC`)\n* Shiranui (`@supersonictw`)"
|
||||
},
|
||||
{
|
||||
"version": "3.4.1",
|
||||
"notes": "* **LocalDocs Fixes:** Several issues with LocalDocs in v3.4.0 have been fixed, including missing words and very slow indexing.\n* **Syntax Highlighting:** Go code is now highlighted with the correct colors.\n* **Cache Fixes:** The model list cache is now stored with a version number, and in a more appropriate directory.\n* **Translation Updates:** The Italian translation has been improved.\n",
|
||||
"contributors": "* Jared Van Bortel (Nomic AI)\n* Adam Treat (Nomic AI)\n* John Parent (Kitware)\n* Riccardo Giovanetti (`@Harvester62`)"
|
||||
},
|
||||
{
|
||||
"version": "3.4.2",
|
||||
"notes": "* **LocalDocs Fixes:** Several issues with LocalDocs, some of which were introduced in v3.4.0, have been fixed.\n * Fixed the possible use of references from unselected collections.\n * Fixed unnecessary reindexing of files with uppercase extensions.\n * Fixed hybrid search failure due to inconsistent database state.\n * Fully fixed the blank Embeddings Device selection in LocalDocs settings.\n * Fixed LocalDocs indexing of large PDFs making very slow progress or even stalling.\n",
|
||||
"contributors": "* Adam Treat (Nomic AI)\n* Jared Van Bortel (Nomic AI)"
|
||||
},
|
||||
{
|
||||
"version": "3.5.0",
|
||||
"notes": "* **Message Editing:**\n * You can now edit any message you've sent by clicking the pencil icon below it.\n * You can now redo earlier responses in the conversation.\n* **Templates:** Chat templates have been completely overhauled! They now use Jinja-style syntax. You may notice warnings or errors in the UI. Read the linked docs, and if you have any questions, please ask on the Discord.\n* **File Attachments:** Markdown and plain text files are now supported as file attachments.\n* **System Tray:** There is now an option in Application Settings to allow GPT4All to minimize to the system tray instead of closing.\n* **Local API Server:**\n * The API server now supports system messages from the client and no longer uses the system message in settings.\n * You can now send messages to the API server in any order supported by the model instead of just user/assistant pairs.\n* **Translations:** The Italian and Romanian translations have been improved.\n",
|
||||
"contributors": "* Jared Van Bortel (Nomic AI)\n* Adam Treat (Nomic AI)\n* Benjamin Gallois (`@bgallois`)\n* Riccardo Giovanetti (`@Harvester62`)\n* Victor Emanuel (`@SINAPSA-IC`)"
|
||||
},
|
||||
{
|
||||
"version": "3.5.1",
|
||||
"notes": "* **Chat template fixes:** Llama 3.2 models, Nous Hermes 2 Mistral, Mistral OpenOrca, Qwen 2 and remote models\n* **Bugfix:** Fix the default model button so it works again after 3.5.0\n",
|
||||
"contributors": "* Jared Van Bortel (Nomic AI)\n* Adam Treat (Nomic AI)"
|
||||
},
|
||||
{
|
||||
"version": "3.5.2",
|
||||
"notes": "* **Model Search:** There are now separate tabs for official and third-party models.\n* **Local Server Fixes:** Several mistakes in v3.5's changes to the API server have been corrected.\n* **Cloned Model Fixes:** The chat template and system message of cloned models now manage their defaults correctly.\n* **Translation Improvements:** The Romanian and Italian translations have been updated.\n",
|
||||
"contributors": "* Jared Van Bortel (Nomic AI)\n* Adam Treat (Nomic AI)\n* Riccardo Giovanetti (`@Harvester62`)\n* Victor Emanuel (`@SINAPSA-IC`)"
|
||||
},
|
||||
{
|
||||
"version": "3.5.3",
|
||||
"notes": "* **LocalDocs Fix:** A serious issue causing LocalDocs to not work properly in v3.5.2 has been fixed.\n",
|
||||
"contributors": "* Jared Van Bortel (Nomic AI)\n* Adam Treat (Nomic AI)"
|
||||
},
|
||||
{
|
||||
"version": "3.6.0",
|
||||
"notes": "* **Reasoner v1:**\n * Built-in javascript code interpreter tool.\n * Custom curated model that utilizes the code interpreter to break down, analyze, perform, and verify complex reasoning tasks.\n* **Templates:** Automatically substitute chat templates that are not compatible with Jinja2Cpp in GGUFs.\n* **Fixes:**\n * Remote model template to allow for XML in messages.\n * Jinja2Cpp bug that broke system message detection in chat templates.\n * LocalDocs sources displaying in unconsolidated form after v3.5.0.\n",
|
||||
"contributors": "* Adam Treat (Nomic AI)\n* Jared Van Bortel (Nomic AI)"
|
||||
},
|
||||
{
|
||||
"version": "3.6.1",
|
||||
"notes": "* **Fixes:**\n * The stop generation button no longer working in v3.6.0.\n * The copy entire conversation button no longer working in v3.6.0.\n",
|
||||
"contributors": "* Adam Treat (Nomic AI)"
|
||||
},
|
||||
{
|
||||
"version": "3.7.0",
|
||||
"notes": "* **Windows ARM Support:** GPT4All now supports the Windows ARM platform, ensuring compatibility with devices powered by Qualcomm Snapdragon and Microsoft SQ-series processors.\n * **NOTE:** Support for GPU and/or NPU acceleration is not available at this time. Only the CPU will be used to run LLMs.\n * **NOTE:** You must install the new *Windows ARM* version of GPT4All from the website. The standard *Windows* version will not work due to emulation limitations.\n* **Fixed Updating on macOS:** The maintenance tool no longer crashes when attempting to update or uninstall GPT4All on Sequoia.\n * **NOTE:** If you have installed the version from the GitHub releases as a workaround for this issue, you can safely uninstall it and switch back to the version from the website.\n* **Fixed Chat Saving on macOS:** Chats now save as expected when the application is quit with Command-Q.\n* **Code Interpreter Improvements:**\n * The behavior when the code takes too long to execute and times out has been improved.\n * console.log now accepts multiple arguments for better compatibility with native JavaScript.\n* **Chat Templating Improvements:**\n * Two crashes and one compatibility issue have been fixed in the chat template parser.\n * The default chat template for EM German Mistral has been fixed.\n * Automatic replacements have been added for five new models as we continue to improve compatibility with common chat templates.\n",
|
||||
"contributors": "* Jared Van Bortel (Nomic AI)\n* Adam Treat (Nomic AI)\n* Riccardo Giovanetti (`@Harvester62`)"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
[tool.pytest.ini_options]
|
||||
addopts = ['--import-mode=importlib']
|
||||
|
||||
[tool.mypy]
|
||||
files = 'tests/python'
|
||||
pretty = true
|
||||
strict = true
|
||||
warn_unused_ignores = false
|
||||
|
||||
[tool.pytype]
|
||||
inputs = ['tests/python']
|
||||
jobs = 'auto'
|
||||
bind_decorated_methods = true
|
||||
none_is_not_bool = true
|
||||
overriding_renamed_parameter_count_checks = true
|
||||
strict_none_binding = true
|
||||
precise_return = true
|
||||
# protocols:
|
||||
# - https://github.com/google/pytype/issues/1423
|
||||
# - https://github.com/google/pytype/issues/1424
|
||||
strict_import = true
|
||||
strict_parameter_checks = true
|
||||
strict_primitive_comparisons = true
|
||||
# strict_undefined_checks: too many false positives
|
||||
|
||||
[tool.isort]
|
||||
src_paths = ['tests/python']
|
||||
line_length = 120
|
||||
combine_as_imports = true
|
||||
@@ -89,8 +89,15 @@ Rectangle {
|
||||
property alias collection: collection.text
|
||||
property alias folder_path: folderEdit.text
|
||||
|
||||
MyFolderDialog {
|
||||
FolderDialog {
|
||||
id: folderDialog
|
||||
title: qsTr("Please choose a directory")
|
||||
}
|
||||
|
||||
function openFolderDialog(currentFolder, onAccepted) {
|
||||
folderDialog.currentFolder = currentFolder;
|
||||
folderDialog.accepted.connect(function() { onAccepted(folderDialog.selectedFolder); });
|
||||
folderDialog.open();
|
||||
}
|
||||
|
||||
Label {
|
||||
@@ -163,7 +170,7 @@ Rectangle {
|
||||
id: browseButton
|
||||
text: qsTr("Browse")
|
||||
onClicked: {
|
||||
folderDialog.openFolderDialog(StandardPaths.writableLocation(StandardPaths.HomeLocation), function(selectedFolder) {
|
||||
root.openFolderDialog(StandardPaths.writableLocation(StandardPaths.HomeLocation), function(selectedFolder) {
|
||||
root.folder_path = selectedFolder
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,592 +0,0 @@
|
||||
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
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
spacing: 5
|
||||
|
||||
Label {
|
||||
Layout.topMargin: 0
|
||||
Layout.bottomMargin: 25
|
||||
Layout.rightMargin: 150 * theme.fontScale
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
verticalAlignment: Text.AlignTop
|
||||
text: qsTr("These models have been specifically configured for use in GPT4All. The first few models on the " +
|
||||
"list are known to work the best, but you should only attempt to use models that will fit in your " +
|
||||
"available memory.")
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
color: theme.textColor
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: !ModelList.gpt4AllDownloadableModels.count && !ModelList.asyncModelRequestOngoing
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
text: qsTr("Network error: could not retrieve %1").arg("http://gpt4all.io/models/models3.json")
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
color: theme.mutedTextColor
|
||||
}
|
||||
|
||||
MyBusyIndicator {
|
||||
visible: !ModelList.gpt4AllDownloadableModels.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")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
ButtonGroup {
|
||||
id: buttonGroup
|
||||
exclusive: true
|
||||
}
|
||||
MyButton {
|
||||
text: qsTr("All")
|
||||
checked: true
|
||||
borderWidth: 0
|
||||
backgroundColor: checked ? theme.lightButtonBackground : "transparent"
|
||||
backgroundColorHovered: theme.lighterButtonBackgroundHovered
|
||||
backgroundRadius: 5
|
||||
padding: 15
|
||||
topPadding: 8
|
||||
bottomPadding: 8
|
||||
textColor: theme.lighterButtonForeground
|
||||
fontPixelSize: theme.fontSizeLarge
|
||||
fontPixelBold: true
|
||||
checkable: true
|
||||
ButtonGroup.group: buttonGroup
|
||||
onClicked: {
|
||||
ModelList.gpt4AllDownloadableModels.filter("");
|
||||
}
|
||||
|
||||
}
|
||||
MyButton {
|
||||
text: qsTr("Reasoning")
|
||||
borderWidth: 0
|
||||
backgroundColor: checked ? theme.lightButtonBackground : "transparent"
|
||||
backgroundColorHovered: theme.lighterButtonBackgroundHovered
|
||||
backgroundRadius: 5
|
||||
padding: 15
|
||||
topPadding: 8
|
||||
bottomPadding: 8
|
||||
textColor: theme.lighterButtonForeground
|
||||
fontPixelSize: theme.fontSizeLarge
|
||||
fontPixelBold: true
|
||||
checkable: true
|
||||
ButtonGroup.group: buttonGroup
|
||||
onClicked: {
|
||||
ModelList.gpt4AllDownloadableModels.filter("#reasoning");
|
||||
}
|
||||
}
|
||||
Layout.bottomMargin: 10
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
|
||||
ListView {
|
||||
id: modelListView
|
||||
model: ModelList.gpt4AllDownloadableModels
|
||||
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: function(link) { Qt.openUrlExternally(link); }
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton // pass clicks to parent
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
|
||||
// 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: !isDownloading && (installed || isIncomplete)
|
||||
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: {
|
||||
var apiKeyText = apiKey.text.trim(),
|
||||
baseUrlText = baseUrl.text.trim(),
|
||||
modelNameText = modelName.text.trim();
|
||||
|
||||
var apiKeyOk = apiKeyText !== "",
|
||||
baseUrlOk = !isCompatibleApi || baseUrlText !== "",
|
||||
modelNameOk = !isCompatibleApi || modelNameText !== "";
|
||||
|
||||
if (!apiKeyOk)
|
||||
apiKey.showError();
|
||||
if (!baseUrlOk)
|
||||
baseUrl.showError();
|
||||
if (!modelNameOk)
|
||||
modelName.showError();
|
||||
|
||||
if (!apiKeyOk || !baseUrlOk || !modelNameOk)
|
||||
return;
|
||||
|
||||
if (!isCompatibleApi)
|
||||
Download.installModel(
|
||||
filename,
|
||||
apiKeyText,
|
||||
);
|
||||
else
|
||||
Download.installCompatibleModel(
|
||||
modelNameText,
|
||||
apiKeyText,
|
||||
baseUrlText,
|
||||
);
|
||||
}
|
||||
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: qsTr("<strong><font size=\"1\"><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. Model requires more memory (%1 GB) than your system has available (%2).</strong></font>").arg(ramrequired).arg(LLM.systemTotalRAMInGBString())
|
||||
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() {
|
||||
messageToast.show(qsTr("ERROR: $API_KEY is empty."));
|
||||
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")
|
||||
}
|
||||
|
||||
MyTextField {
|
||||
id: baseUrl
|
||||
visible: !installed && isOnline && isCompatibleApi
|
||||
Layout.topMargin: 20
|
||||
Layout.leftMargin: 20
|
||||
Layout.minimumWidth: 200
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||
wrapMode: Text.WrapAnywhere
|
||||
function showError() {
|
||||
messageToast.show(qsTr("ERROR: $BASE_URL is empty."));
|
||||
baseUrl.placeholderTextColor = theme.textErrorColor;
|
||||
}
|
||||
onTextChanged: {
|
||||
baseUrl.placeholderTextColor = theme.mutedTextColor;
|
||||
}
|
||||
placeholderText: qsTr("enter $BASE_URL")
|
||||
Accessible.role: Accessible.EditableText
|
||||
Accessible.name: placeholderText
|
||||
Accessible.description: qsTr("Whether the file hash is being calculated")
|
||||
}
|
||||
|
||||
MyTextField {
|
||||
id: modelName
|
||||
visible: !installed && isOnline && isCompatibleApi
|
||||
Layout.topMargin: 20
|
||||
Layout.leftMargin: 20
|
||||
Layout.minimumWidth: 200
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||
wrapMode: Text.WrapAnywhere
|
||||
function showError() {
|
||||
messageToast.show(qsTr("ERROR: $MODEL_NAME is empty."))
|
||||
modelName.placeholderTextColor = theme.textErrorColor;
|
||||
}
|
||||
onTextChanged: {
|
||||
modelName.placeholderTextColor = theme.mutedTextColor;
|
||||
}
|
||||
placeholderText: qsTr("enter $MODEL_NAME")
|
||||
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.fontSizeSmall
|
||||
color: theme.mutedDarkTextColor
|
||||
}
|
||||
Text {
|
||||
text: filesize
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
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.fontSizeSmall
|
||||
color: theme.mutedDarkTextColor
|
||||
}
|
||||
Text {
|
||||
text: ramrequired >= 0 ? qsTr("%1 GB").arg(ramrequired) : qsTr("?")
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
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.fontSizeSmall
|
||||
color: theme.mutedDarkTextColor
|
||||
}
|
||||
Text {
|
||||
text: parameters !== "" ? parameters : qsTr("?")
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
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.fontSizeSmall
|
||||
color: theme.mutedDarkTextColor
|
||||
}
|
||||
Text {
|
||||
text: quant
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
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.fontSizeSmall
|
||||
color: theme.mutedDarkTextColor
|
||||
}
|
||||
Text {
|
||||
text: type
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,703 +0,0 @@
|
||||
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
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
spacing: 5
|
||||
|
||||
Label {
|
||||
Layout.topMargin: 0
|
||||
Layout.bottomMargin: 25
|
||||
Layout.rightMargin: 150 * theme.fontScale
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
verticalAlignment: Text.AlignTop
|
||||
text: qsTr("Use the search to find and download models from HuggingFace. There is NO GUARANTEE that these " +
|
||||
"will work. Many will require additional configuration before they can be used.")
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
color: theme.textColor
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: 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
|
||||
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 %1").arg(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.huggingFaceDownloadableModels.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 !== ""
|
||||
source: "qrc:/gpt4all/icons/close.svg"
|
||||
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: ListModel {
|
||||
ListElement { name: qsTr("Default") }
|
||||
ListElement { name: qsTr("Likes") }
|
||||
ListElement { name: qsTr("Downloads") }
|
||||
ListElement { name: qsTr("Recent") }
|
||||
}
|
||||
currentIndex: ModelList.discoverSort
|
||||
contentItem: Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
rightPadding: 30
|
||||
color: theme.textColor
|
||||
text: {
|
||||
return qsTr("Sort by: %1").arg(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: ListModel {
|
||||
ListElement { name: qsTr("Asc") }
|
||||
ListElement { name: 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: %1").arg(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: ListModel {
|
||||
ListElement { name: "5" }
|
||||
ListElement { name: "10" }
|
||||
ListElement { name: "20" }
|
||||
ListElement { name: "50" }
|
||||
ListElement { name: "100" }
|
||||
ListElement { name: 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: %1").arg(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
|
||||
ListView {
|
||||
id: modelListView
|
||||
model: ModelList.huggingFaceDownloadableModels
|
||||
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: function(link) { Qt.openUrlExternally(link); }
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton // pass clicks to parent
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
|
||||
// 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: !isDownloading && (installed || isIncomplete)
|
||||
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: {
|
||||
var apiKeyText = apiKey.text.trim(),
|
||||
baseUrlText = baseUrl.text.trim(),
|
||||
modelNameText = modelName.text.trim();
|
||||
|
||||
var apiKeyOk = apiKeyText !== "",
|
||||
baseUrlOk = !isCompatibleApi || baseUrlText !== "",
|
||||
modelNameOk = !isCompatibleApi || modelNameText !== "";
|
||||
|
||||
if (!apiKeyOk)
|
||||
apiKey.showError();
|
||||
if (!baseUrlOk)
|
||||
baseUrl.showError();
|
||||
if (!modelNameOk)
|
||||
modelName.showError();
|
||||
|
||||
if (!apiKeyOk || !baseUrlOk || !modelNameOk)
|
||||
return;
|
||||
|
||||
if (!isCompatibleApi)
|
||||
Download.installModel(
|
||||
filename,
|
||||
apiKeyText,
|
||||
);
|
||||
else
|
||||
Download.installCompatibleModel(
|
||||
modelNameText,
|
||||
apiKeyText,
|
||||
baseUrlText,
|
||||
);
|
||||
}
|
||||
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: qsTr("<strong><font size=\"1\"><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. Model requires more memory (%1 GB) than your system has available (%2).</strong></font>").arg(ramrequired).arg(LLM.systemTotalRAMInGBString())
|
||||
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() {
|
||||
messageToast.show(qsTr("ERROR: $API_KEY is empty."));
|
||||
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")
|
||||
}
|
||||
|
||||
MyTextField {
|
||||
id: baseUrl
|
||||
visible: !installed && isOnline && isCompatibleApi
|
||||
Layout.topMargin: 20
|
||||
Layout.leftMargin: 20
|
||||
Layout.minimumWidth: 200
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||
wrapMode: Text.WrapAnywhere
|
||||
function showError() {
|
||||
messageToast.show(qsTr("ERROR: $BASE_URL is empty."));
|
||||
baseUrl.placeholderTextColor = theme.textErrorColor;
|
||||
}
|
||||
onTextChanged: {
|
||||
baseUrl.placeholderTextColor = theme.mutedTextColor;
|
||||
}
|
||||
placeholderText: qsTr("enter $BASE_URL")
|
||||
Accessible.role: Accessible.EditableText
|
||||
Accessible.name: placeholderText
|
||||
Accessible.description: qsTr("Whether the file hash is being calculated")
|
||||
}
|
||||
|
||||
MyTextField {
|
||||
id: modelName
|
||||
visible: !installed && isOnline && isCompatibleApi
|
||||
Layout.topMargin: 20
|
||||
Layout.leftMargin: 20
|
||||
Layout.minimumWidth: 200
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||
wrapMode: Text.WrapAnywhere
|
||||
function showError() {
|
||||
messageToast.show(qsTr("ERROR: $MODEL_NAME is empty."))
|
||||
modelName.placeholderTextColor = theme.textErrorColor;
|
||||
}
|
||||
onTextChanged: {
|
||||
modelName.placeholderTextColor = theme.mutedTextColor;
|
||||
}
|
||||
placeholderText: qsTr("enter $MODEL_NAME")
|
||||
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.fontSizeSmall
|
||||
color: theme.mutedDarkTextColor
|
||||
}
|
||||
Text {
|
||||
text: filesize
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
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.fontSizeSmall
|
||||
color: theme.mutedDarkTextColor
|
||||
}
|
||||
Text {
|
||||
text: quant
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
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.fontSizeSmall
|
||||
color: theme.mutedDarkTextColor
|
||||
}
|
||||
Text {
|
||||
text: type
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,12 +42,12 @@ Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 30
|
||||
spacing: 10
|
||||
spacing: 30
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
spacing: 10
|
||||
spacing: 30
|
||||
|
||||
MyButton {
|
||||
id: backButton
|
||||
@@ -76,60 +76,732 @@ Rectangle {
|
||||
font.pixelSize: theme.fontSizeBanner
|
||||
color: theme.titleTextColor
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: bar
|
||||
implicitWidth: 600
|
||||
spacing: 10
|
||||
MyTabButton {
|
||||
text: qsTr("GPT4All")
|
||||
isSelected: gpt4AllModelView.isShown()
|
||||
onPressed: {
|
||||
gpt4AllModelView.show();
|
||||
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
|
||||
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 %1").arg(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 !== ""
|
||||
source: "qrc:/gpt4all/icons/close.svg"
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MyTabButton {
|
||||
text: qsTr("HuggingFace")
|
||||
isSelected: huggingfaceModelView.isShown()
|
||||
onPressed: {
|
||||
huggingfaceModelView.show();
|
||||
|
||||
RowLayout {
|
||||
id: discoveryTools
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.margins: 0
|
||||
spacing: 20
|
||||
visible: false
|
||||
MyComboBox {
|
||||
id: comboSort
|
||||
model: ListModel {
|
||||
ListElement { name: qsTr("Default") }
|
||||
ListElement { name: qsTr("Likes") }
|
||||
ListElement { name: qsTr("Downloads") }
|
||||
ListElement { name: qsTr("Recent") }
|
||||
}
|
||||
currentIndex: ModelList.discoverSort
|
||||
contentItem: Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
rightPadding: 30
|
||||
color: theme.textColor
|
||||
text: {
|
||||
return qsTr("Sort by: %1").arg(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: ListModel {
|
||||
ListElement { name: qsTr("Asc") }
|
||||
ListElement { name: 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: %1").arg(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: ListModel {
|
||||
ListElement { name: "5" }
|
||||
ListElement { name: "10" }
|
||||
ListElement { name: "20" }
|
||||
ListElement { name: "50" }
|
||||
ListElement { name: "100" }
|
||||
ListElement { name: 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: %1").arg(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
id: stackLayout
|
||||
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 %1").arg("http://gpt4all.io/models/models3.json")
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
color: theme.mutedTextColor
|
||||
}
|
||||
|
||||
AddGPT4AllModelView {
|
||||
id: gpt4AllModelView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
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")
|
||||
}
|
||||
|
||||
function show() {
|
||||
stackLayout.currentIndex = 0;
|
||||
}
|
||||
function isShown() {
|
||||
return stackLayout.currentIndex === 0
|
||||
}
|
||||
}
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
|
||||
AddHFModelView {
|
||||
id: huggingfaceModelView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
// FIXME: This generates a warning and should not be used inside a layout, but without
|
||||
// it the text field inside this qml does not display at full width so it looks like
|
||||
// a bug in stacklayout
|
||||
anchors.fill: parent
|
||||
ListView {
|
||||
id: modelListView
|
||||
model: ModelList.downloadableModels
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
spacing: 30
|
||||
|
||||
function show() {
|
||||
stackLayout.currentIndex = 1;
|
||||
}
|
||||
function isShown() {
|
||||
return stackLayout.currentIndex === 1
|
||||
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: function(link) { Qt.openUrlExternally(link); }
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton // pass clicks to parent
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
|
||||
// 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: !isDownloading && (installed || isIncomplete)
|
||||
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: {
|
||||
var apiKeyText = apiKey.text.trim(),
|
||||
baseUrlText = baseUrl.text.trim(),
|
||||
modelNameText = modelName.text.trim();
|
||||
|
||||
var apiKeyOk = apiKeyText !== "",
|
||||
baseUrlOk = !isCompatibleApi || baseUrlText !== "",
|
||||
modelNameOk = !isCompatibleApi || modelNameText !== "";
|
||||
|
||||
if (!apiKeyOk)
|
||||
apiKey.showError();
|
||||
if (!baseUrlOk)
|
||||
baseUrl.showError();
|
||||
if (!modelNameOk)
|
||||
modelName.showError();
|
||||
|
||||
if (!apiKeyOk || !baseUrlOk || !modelNameOk)
|
||||
return;
|
||||
|
||||
if (!isCompatibleApi)
|
||||
Download.installModel(
|
||||
filename,
|
||||
apiKeyText,
|
||||
);
|
||||
else
|
||||
Download.installCompatibleModel(
|
||||
modelNameText,
|
||||
apiKeyText,
|
||||
baseUrlText,
|
||||
);
|
||||
}
|
||||
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: qsTr("<strong><font size=\"1\"><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. Model requires more memory (%1 GB) than your system has available (%2).</strong></font>").arg(ramrequired).arg(LLM.systemTotalRAMInGBString())
|
||||
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() {
|
||||
messageToast.show(qsTr("ERROR: $API_KEY is empty."));
|
||||
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")
|
||||
}
|
||||
|
||||
MyTextField {
|
||||
id: baseUrl
|
||||
visible: !installed && isOnline && isCompatibleApi
|
||||
Layout.topMargin: 20
|
||||
Layout.leftMargin: 20
|
||||
Layout.minimumWidth: 200
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||
wrapMode: Text.WrapAnywhere
|
||||
function showError() {
|
||||
messageToast.show(qsTr("ERROR: $BASE_URL is empty."));
|
||||
baseUrl.placeholderTextColor = theme.textErrorColor;
|
||||
}
|
||||
onTextChanged: {
|
||||
baseUrl.placeholderTextColor = theme.mutedTextColor;
|
||||
}
|
||||
placeholderText: qsTr("enter $BASE_URL")
|
||||
Accessible.role: Accessible.EditableText
|
||||
Accessible.name: placeholderText
|
||||
Accessible.description: qsTr("Whether the file hash is being calculated")
|
||||
}
|
||||
|
||||
MyTextField {
|
||||
id: modelName
|
||||
visible: !installed && isOnline && isCompatibleApi
|
||||
Layout.topMargin: 20
|
||||
Layout.leftMargin: 20
|
||||
Layout.minimumWidth: 200
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||
wrapMode: Text.WrapAnywhere
|
||||
function showError() {
|
||||
messageToast.show(qsTr("ERROR: $MODEL_NAME is empty."))
|
||||
modelName.placeholderTextColor = theme.textErrorColor;
|
||||
}
|
||||
onTextChanged: {
|
||||
modelName.placeholderTextColor = theme.mutedTextColor;
|
||||
}
|
||||
placeholderText: qsTr("enter $MODEL_NAME")
|
||||
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.fontSizeSmall
|
||||
color: theme.mutedDarkTextColor
|
||||
}
|
||||
Text {
|
||||
text: filesize
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
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.fontSizeSmall
|
||||
color: theme.mutedDarkTextColor
|
||||
}
|
||||
Text {
|
||||
text: ramrequired >= 0 ? qsTr("%1 GB").arg(ramrequired) : qsTr("?")
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
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.fontSizeSmall
|
||||
color: theme.mutedDarkTextColor
|
||||
}
|
||||
Text {
|
||||
text: parameters !== "" ? parameters : qsTr("?")
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
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.fontSizeSmall
|
||||
color: theme.mutedDarkTextColor
|
||||
}
|
||||
Text {
|
||||
text: quant
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
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.fontSizeSmall
|
||||
color: theme.mutedDarkTextColor
|
||||
}
|
||||
Text {
|
||||
text: type
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import network
|
||||
import llm
|
||||
|
||||
MySettingsTab {
|
||||
onRestoreDefaults: {
|
||||
onRestoreDefaultsClicked: {
|
||||
MySettings.restoreApplicationDefaults();
|
||||
}
|
||||
title: qsTr("Application")
|
||||
@@ -394,14 +394,11 @@ MySettingsTab {
|
||||
}
|
||||
}
|
||||
}
|
||||
MyFolderDialog {
|
||||
id: folderDialog
|
||||
}
|
||||
MySettingsButton {
|
||||
text: qsTr("Browse")
|
||||
Accessible.description: qsTr("Choose where to save model files")
|
||||
onClicked: {
|
||||
folderDialog.openFolderDialog("file://" + MySettings.modelPath, function(selectedFolder) {
|
||||
openFolderDialog("file://" + MySettings.modelPath, function(selectedFolder) {
|
||||
MySettings.modelPath = selectedFolder
|
||||
})
|
||||
}
|
||||
@@ -487,32 +484,32 @@ MySettingsTab {
|
||||
Accessible.description: ToolTip.text
|
||||
}
|
||||
MySettingsLabel {
|
||||
id: trayLabel
|
||||
text: qsTr("Enable System Tray")
|
||||
helpText: qsTr("The application will minimize to the system tray when the window is closed.")
|
||||
Layout.row: 13
|
||||
id: saveChatsContextLabel
|
||||
text: qsTr("Save Chat Context")
|
||||
helpText: qsTr("Save the chat model's state to disk for faster loading. WARNING: Uses ~2GB per chat.")
|
||||
Layout.row: 12
|
||||
Layout.column: 0
|
||||
}
|
||||
MyCheckBox {
|
||||
id: trayBox
|
||||
Layout.row: 13
|
||||
id: saveChatsContextBox
|
||||
Layout.row: 12
|
||||
Layout.column: 2
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: MySettings.systemTray
|
||||
checked: MySettings.saveChatsContext
|
||||
onClicked: {
|
||||
MySettings.systemTray = !MySettings.systemTray
|
||||
MySettings.saveChatsContext = !MySettings.saveChatsContext
|
||||
}
|
||||
}
|
||||
MySettingsLabel {
|
||||
id: serverChatLabel
|
||||
text: qsTr("Enable Local API Server")
|
||||
helpText: qsTr("Expose an OpenAI-Compatible server to localhost. WARNING: Results in increased resource usage.")
|
||||
Layout.row: 14
|
||||
Layout.row: 13
|
||||
Layout.column: 0
|
||||
}
|
||||
MyCheckBox {
|
||||
id: serverChatBox
|
||||
Layout.row: 14
|
||||
Layout.row: 13
|
||||
Layout.column: 2
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: MySettings.serverChat
|
||||
@@ -524,7 +521,7 @@ MySettingsTab {
|
||||
id: serverPortLabel
|
||||
text: qsTr("API Server Port")
|
||||
helpText: qsTr("The port to use for the local server. Requires restart.")
|
||||
Layout.row: 15
|
||||
Layout.row: 14
|
||||
Layout.column: 0
|
||||
}
|
||||
MyTextField {
|
||||
@@ -532,7 +529,7 @@ MySettingsTab {
|
||||
text: MySettings.networkPort
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
Layout.row: 15
|
||||
Layout.row: 14
|
||||
Layout.column: 2
|
||||
Layout.minimumWidth: 200
|
||||
Layout.maximumWidth: 200
|
||||
@@ -577,12 +574,12 @@ MySettingsTab {
|
||||
id: updatesLabel
|
||||
text: qsTr("Check For Updates")
|
||||
helpText: qsTr("Manually check for an update to GPT4All.");
|
||||
Layout.row: 16
|
||||
Layout.row: 15
|
||||
Layout.column: 0
|
||||
}
|
||||
|
||||
MySettingsButton {
|
||||
Layout.row: 16
|
||||
Layout.row: 15
|
||||
Layout.column: 2
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: qsTr("Updates");
|
||||
@@ -593,7 +590,7 @@ MySettingsTab {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.row: 17
|
||||
Layout.row: 16
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 3
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import QtQuick.Layouts
|
||||
|
||||
import gpt4all
|
||||
import mysettings
|
||||
import toolenums
|
||||
|
||||
ColumnLayout {
|
||||
property alias textContent: innerTextItem.textContent
|
||||
property bool isCurrent: false
|
||||
property bool isError: false
|
||||
property bool isThinking: false
|
||||
property int thinkingTime: 0
|
||||
|
||||
Layout.topMargin: 10
|
||||
Layout.bottomMargin: 10
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: childrenRect.width
|
||||
Layout.preferredHeight: 38
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: myTextArea.implicitWidth
|
||||
Layout.preferredHeight: myTextArea.implicitHeight
|
||||
TextArea {
|
||||
id: myTextArea
|
||||
text: {
|
||||
if (isError)
|
||||
return qsTr("Analysis encountered error");
|
||||
if (isCurrent)
|
||||
return isThinking ? qsTr("Thinking") : qsTr("Analyzing");
|
||||
return isThinking
|
||||
? qsTr("Thought for %1 %2")
|
||||
.arg(Math.ceil(thinkingTime / 1000.0))
|
||||
.arg(Math.ceil(thinkingTime / 1000.0) === 1 ? qsTr("second") : qsTr("seconds"))
|
||||
: qsTr("Analyzed");
|
||||
}
|
||||
padding: 0
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
enabled: false
|
||||
focus: false
|
||||
readOnly: true
|
||||
color: headerMA.containsMouse ? theme.mutedDarkTextColorHovered : theme.mutedTextColor
|
||||
hoverEnabled: false
|
||||
}
|
||||
|
||||
Item {
|
||||
id: textColorOverlay
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
visible: false
|
||||
Rectangle {
|
||||
id: animationRec
|
||||
width: myTextArea.width * 0.3
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
color: theme.textColor
|
||||
|
||||
SequentialAnimation {
|
||||
running: isCurrent
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
target: animationRec;
|
||||
property: "x";
|
||||
from: -animationRec.width;
|
||||
to: myTextArea.width * 3;
|
||||
duration: 2000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OpacityMask {
|
||||
visible: isCurrent
|
||||
anchors.fill: parent
|
||||
maskSource: myTextArea
|
||||
source: textColorOverlay
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: caret
|
||||
Layout.preferredWidth: contentCaret.width
|
||||
Layout.preferredHeight: contentCaret.height
|
||||
Image {
|
||||
id: contentCaret
|
||||
anchors.centerIn: parent
|
||||
visible: false
|
||||
sourceSize.width: theme.fontSizeLarge
|
||||
sourceSize.height: theme.fontSizeLarge
|
||||
mipmap: true
|
||||
source: {
|
||||
if (contentLayout.state === "collapsed")
|
||||
return "qrc:/gpt4all/icons/caret_right.svg";
|
||||
else
|
||||
return "qrc:/gpt4all/icons/caret_down.svg";
|
||||
}
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: contentCaret
|
||||
source: contentCaret
|
||||
color: headerMA.containsMouse ? theme.mutedDarkTextColorHovered : theme.mutedTextColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: headerMA
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (contentLayout.state === "collapsed")
|
||||
contentLayout.state = "expanded";
|
||||
else
|
||||
contentLayout.state = "collapsed";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout
|
||||
spacing: 0
|
||||
state: "collapsed"
|
||||
clip: true
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "expanded"
|
||||
PropertyChanges { target: contentLayout; Layout.preferredHeight: innerContentLayout.height }
|
||||
},
|
||||
State {
|
||||
name: "collapsed"
|
||||
PropertyChanges { target: contentLayout; Layout.preferredHeight: 0 }
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
SequentialAnimation {
|
||||
PropertyAnimation {
|
||||
target: contentLayout
|
||||
property: "Layout.preferredHeight"
|
||||
duration: 300
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
ColumnLayout {
|
||||
id: innerContentLayout
|
||||
Layout.leftMargin: 30
|
||||
ChatTextItem {
|
||||
id: innerTextItem
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,831 +0,0 @@
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import QtQuick.Layouts
|
||||
import Qt.labs.qmlmodels
|
||||
|
||||
import gpt4all
|
||||
import mysettings
|
||||
import toolenums
|
||||
|
||||
ColumnLayout {
|
||||
|
||||
property var inputBoxText: null
|
||||
signal setInputBoxText(text: string)
|
||||
|
||||
Item {
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredHeight: gridLayout.height
|
||||
|
||||
HoverHandler { id: hoverArea }
|
||||
|
||||
GridLayout {
|
||||
id: gridLayout
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
columns: 2
|
||||
|
||||
Item {
|
||||
Layout.row: 0
|
||||
Layout.column: 0
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
Layout.preferredWidth: 32
|
||||
Layout.preferredHeight: 32
|
||||
Layout.topMargin: model.index > 0 ? 25 : 0
|
||||
|
||||
Image {
|
||||
id: logo
|
||||
sourceSize: Qt.size(32, 32)
|
||||
fillMode: Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
visible: false
|
||||
source: name !== "Response: " ? "qrc:/gpt4all/icons/you.svg" : "qrc:/gpt4all/icons/gpt4all_transparent.svg"
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
id: colorOver
|
||||
anchors.fill: logo
|
||||
source: logo
|
||||
color: theme.conversationHeader
|
||||
RotationAnimation {
|
||||
id: rotationAnimation
|
||||
target: colorOver
|
||||
property: "rotation"
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
running: isCurrentResponse && currentChat.responseInProgress
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.row: 0
|
||||
Layout.column: 1
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 38
|
||||
Layout.topMargin: model.index > 0 ? 25 : 0
|
||||
|
||||
RowLayout {
|
||||
spacing: 5
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
TextArea {
|
||||
text: {
|
||||
if (name === "Response: ")
|
||||
return qsTr("GPT4All");
|
||||
return qsTr("You");
|
||||
}
|
||||
padding: 0
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
font.bold: true
|
||||
color: theme.conversationHeader
|
||||
enabled: false
|
||||
focus: false
|
||||
readOnly: true
|
||||
}
|
||||
Text {
|
||||
visible: name === "Response: "
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
text: currentModelName()
|
||||
color: theme.mutedTextColor
|
||||
}
|
||||
RowLayout {
|
||||
visible: isCurrentResponse && (content === "" && currentChat.responseInProgress)
|
||||
Text {
|
||||
color: theme.mutedTextColor
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
text: {
|
||||
switch (currentChat.responseState) {
|
||||
case Chat.ResponseStopped: return qsTr("response stopped ...");
|
||||
case Chat.LocalDocsRetrieval: return qsTr("retrieving localdocs: %1 ...").arg(currentChat.collectionList.join(", "));
|
||||
case Chat.LocalDocsProcessing: return qsTr("searching localdocs: %1 ...").arg(currentChat.collectionList.join(", "));
|
||||
case Chat.PromptProcessing: return qsTr("processing ...")
|
||||
case Chat.ResponseGeneration: return qsTr("generating response ...");
|
||||
case Chat.GeneratingQuestions: return qsTr("generating questions ...");
|
||||
case Chat.ToolCallGeneration: return qsTr("generating toolcall ...");
|
||||
default: return ""; // handle unexpected values
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.row: 1
|
||||
Layout.column: 1
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
Flow {
|
||||
id: attachedUrlsFlow
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 10
|
||||
spacing: 10
|
||||
visible: promptAttachments.length !== 0
|
||||
Repeater {
|
||||
model: promptAttachments
|
||||
|
||||
delegate: Rectangle {
|
||||
width: 350
|
||||
height: 50
|
||||
radius: 5
|
||||
color: theme.attachmentBackground
|
||||
border.color: theme.controlBorder
|
||||
|
||||
Row {
|
||||
spacing: 5
|
||||
anchors.fill: parent
|
||||
anchors.margins: 5
|
||||
|
||||
MyFileIcon {
|
||||
iconSize: 40
|
||||
fileName: modelData.file
|
||||
}
|
||||
|
||||
Text {
|
||||
width: 295
|
||||
height: 40
|
||||
text: modelData.file
|
||||
color: theme.textColor
|
||||
horizontalAlignment: Text.AlignHLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: theme.fontSizeMedium
|
||||
font.bold: true
|
||||
wrapMode: Text.WrapAnywhere
|
||||
elide: Qt.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: childItems
|
||||
|
||||
DelegateChooser {
|
||||
id: chooser
|
||||
role: "name"
|
||||
DelegateChoice {
|
||||
roleValue: "Text: ";
|
||||
ChatTextItem {
|
||||
Layout.fillWidth: true
|
||||
textContent: modelData.content
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: "ToolCall: ";
|
||||
ChatCollapsibleItem {
|
||||
Layout.fillWidth: true
|
||||
textContent: modelData.content
|
||||
isCurrent: modelData.isCurrentResponse
|
||||
isError: modelData.isToolCallError
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: "Think: ";
|
||||
ChatCollapsibleItem {
|
||||
Layout.fillWidth: true
|
||||
textContent: modelData.content
|
||||
isCurrent: modelData.isCurrentResponse
|
||||
isError: false
|
||||
isThinking: true
|
||||
thinkingTime: modelData.thinkingTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: chooser
|
||||
}
|
||||
|
||||
ChatTextItem {
|
||||
Layout.fillWidth: true
|
||||
textContent: content
|
||||
}
|
||||
|
||||
ThumbsDownDialog {
|
||||
id: thumbsDownDialog
|
||||
x: Math.round((parent.width - width) / 2)
|
||||
y: Math.round((parent.height - height) / 2)
|
||||
width: 640
|
||||
height: 300
|
||||
property string text: content
|
||||
response: newResponse === undefined || newResponse === "" ? text : newResponse
|
||||
onAccepted: {
|
||||
var responseHasChanged = response !== text && response !== newResponse
|
||||
if (thumbsDownState && !thumbsUpState && !responseHasChanged)
|
||||
return
|
||||
|
||||
chatModel.updateNewResponse(model.index, response)
|
||||
chatModel.updateThumbsUpState(model.index, false)
|
||||
chatModel.updateThumbsDownState(model.index, true)
|
||||
Network.sendConversation(currentChat.id, getConversationJson());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.row: 2
|
||||
Layout.column: 1
|
||||
Layout.topMargin: 5
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: childrenRect.width
|
||||
Layout.preferredHeight: childrenRect.height
|
||||
visible: {
|
||||
if (name !== "Response: ")
|
||||
return false
|
||||
if (consolidatedSources.length === 0)
|
||||
return false
|
||||
if (!MySettings.localDocsShowReferences)
|
||||
return false
|
||||
if (isCurrentResponse && currentChat.responseInProgress
|
||||
&& currentChat.responseState !== Chat.GeneratingQuestions )
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
MyButton {
|
||||
backgroundColor: theme.sourcesBackground
|
||||
backgroundColorHovered: theme.sourcesBackgroundHovered
|
||||
contentItem: RowLayout {
|
||||
anchors.centerIn: parent
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
|
||||
Image {
|
||||
id: sourcesIcon
|
||||
visible: false
|
||||
anchors.fill: parent
|
||||
sourceSize.width: 24
|
||||
sourceSize.height: 24
|
||||
mipmap: true
|
||||
source: "qrc:/gpt4all/icons/db.svg"
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: sourcesIcon
|
||||
source: sourcesIcon
|
||||
color: theme.textColor
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: qsTr("%n Source(s)", "", consolidatedSources.length)
|
||||
padding: 0
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
font.bold: true
|
||||
color: theme.styledTextColor
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: caret.width
|
||||
Layout.preferredHeight: caret.height
|
||||
Image {
|
||||
id: caret
|
||||
anchors.centerIn: parent
|
||||
visible: false
|
||||
sourceSize.width: theme.fontSizeLarge
|
||||
sourceSize.height: theme.fontSizeLarge
|
||||
mipmap: true
|
||||
source: {
|
||||
if (sourcesLayout.state === "collapsed")
|
||||
return "qrc:/gpt4all/icons/caret_right.svg";
|
||||
else
|
||||
return "qrc:/gpt4all/icons/caret_down.svg";
|
||||
}
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: caret
|
||||
source: caret
|
||||
color: theme.textColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (sourcesLayout.state === "collapsed")
|
||||
sourcesLayout.state = "expanded";
|
||||
else
|
||||
sourcesLayout.state = "collapsed";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: sourcesLayout
|
||||
Layout.row: 3
|
||||
Layout.column: 1
|
||||
Layout.topMargin: 5
|
||||
visible: {
|
||||
if (consolidatedSources.length === 0)
|
||||
return false
|
||||
if (!MySettings.localDocsShowReferences)
|
||||
return false
|
||||
if (isCurrentResponse && currentChat.responseInProgress
|
||||
&& currentChat.responseState !== Chat.GeneratingQuestions )
|
||||
return false
|
||||
return true
|
||||
}
|
||||
clip: true
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 0
|
||||
state: "collapsed"
|
||||
states: [
|
||||
State {
|
||||
name: "expanded"
|
||||
PropertyChanges { target: sourcesLayout; Layout.preferredHeight: sourcesFlow.height }
|
||||
},
|
||||
State {
|
||||
name: "collapsed"
|
||||
PropertyChanges { target: sourcesLayout; Layout.preferredHeight: 0 }
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
SequentialAnimation {
|
||||
PropertyAnimation {
|
||||
target: sourcesLayout
|
||||
property: "Layout.preferredHeight"
|
||||
duration: 300
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Flow {
|
||||
id: sourcesFlow
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
visible: consolidatedSources.length !== 0
|
||||
Repeater {
|
||||
model: consolidatedSources
|
||||
|
||||
delegate: Rectangle {
|
||||
radius: 10
|
||||
color: ma.containsMouse ? theme.sourcesBackgroundHovered : theme.sourcesBackground
|
||||
width: 200
|
||||
height: 75
|
||||
|
||||
MouseArea {
|
||||
id: ma
|
||||
enabled: modelData.path !== ""
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: function() {
|
||||
Qt.openUrlExternally(modelData.fileUri)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: debugTooltip
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
width: 24
|
||||
height: 24
|
||||
color: "transparent"
|
||||
ToolTip {
|
||||
parent: debugTooltip
|
||||
visible: debugMouseArea.containsMouse
|
||||
text: modelData.text
|
||||
contentWidth: 900
|
||||
delay: 500
|
||||
}
|
||||
MouseArea {
|
||||
id: debugMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 10
|
||||
spacing: 0
|
||||
RowLayout {
|
||||
id: title
|
||||
spacing: 5
|
||||
Layout.maximumWidth: 180
|
||||
MyFileIcon {
|
||||
iconSize: 24
|
||||
fileName: modelData.file
|
||||
Layout.preferredWidth: iconSize
|
||||
Layout.preferredHeight: iconSize
|
||||
}
|
||||
Text {
|
||||
Layout.maximumWidth: 156
|
||||
text: modelData.collection !== "" ? modelData.collection : qsTr("LocalDocs")
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
font.bold: true
|
||||
color: theme.styledTextColor
|
||||
elide: Qt.ElideRight
|
||||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
color: "transparent"
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
Text {
|
||||
Layout.fillHeight: true
|
||||
Layout.maximumWidth: 180
|
||||
Layout.maximumHeight: 55 - title.height
|
||||
text: modelData.file
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
elide: Qt.ElideRight
|
||||
wrapMode: Text.WrapAnywhere
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmationDialog {
|
||||
id: editPromptDialog
|
||||
dialogTitle: qsTr("Edit this message?")
|
||||
description: qsTr("All following messages will be permanently erased.")
|
||||
onAccepted: {
|
||||
const msg = currentChat.popPrompt(index);
|
||||
if (msg !== null)
|
||||
setInputBoxText(msg);
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmationDialog {
|
||||
id: redoResponseDialog
|
||||
dialogTitle: qsTr("Redo this response?")
|
||||
description: qsTr("All following messages will be permanently erased.")
|
||||
onAccepted: currentChat.regenerateResponse(index)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: buttonRow
|
||||
Layout.row: 4
|
||||
Layout.column: 1
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
spacing: 3
|
||||
visible: !isCurrentResponse || !currentChat.responseInProgress
|
||||
enabled: opacity > 0
|
||||
opacity: hoverArea.hovered
|
||||
|
||||
Behavior on opacity {
|
||||
OpacityAnimator { duration: 30 }
|
||||
}
|
||||
|
||||
ChatMessageButton {
|
||||
readonly property var editingDisabledReason: {
|
||||
if (!currentChat.isModelLoaded)
|
||||
return qsTr("Cannot edit chat without a loaded model.");
|
||||
if (currentChat.responseInProgress)
|
||||
return qsTr("Cannot edit chat while the model is generating.");
|
||||
return null;
|
||||
}
|
||||
visible: !currentChat.isServer && model.name === "Prompt: "
|
||||
enabled: editingDisabledReason === null
|
||||
Layout.maximumWidth: 24
|
||||
Layout.maximumHeight: 24
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: false
|
||||
name: editingDisabledReason ?? qsTr("Edit")
|
||||
source: "qrc:/gpt4all/icons/edit.svg"
|
||||
onClicked: {
|
||||
if (inputBoxText === "")
|
||||
editPromptDialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
ChatMessageButton {
|
||||
readonly property var editingDisabledReason: {
|
||||
if (!currentChat.isModelLoaded)
|
||||
return qsTr("Cannot redo response without a loaded model.");
|
||||
if (currentChat.responseInProgress)
|
||||
return qsTr("Cannot redo response while the model is generating.");
|
||||
return null;
|
||||
}
|
||||
visible: !currentChat.isServer && model.name === "Response: "
|
||||
enabled: editingDisabledReason === null
|
||||
Layout.maximumWidth: 24
|
||||
Layout.maximumHeight: 24
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: false
|
||||
name: editingDisabledReason ?? qsTr("Redo")
|
||||
source: "qrc:/gpt4all/icons/regenerate.svg"
|
||||
onClicked: {
|
||||
if (index == chatModel.count - 1) {
|
||||
// regenerate last message without confirmation
|
||||
currentChat.regenerateResponse(index);
|
||||
return;
|
||||
}
|
||||
redoResponseDialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
ChatMessageButton {
|
||||
Layout.maximumWidth: 24
|
||||
Layout.maximumHeight: 24
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: false
|
||||
name: qsTr("Copy")
|
||||
source: "qrc:/gpt4all/icons/copy.svg"
|
||||
onClicked: {
|
||||
chatModel.copyToClipboard(index);
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: name === "Response: " && MySettings.networkIsActive
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: childrenRect.width
|
||||
Layout.preferredHeight: childrenRect.height
|
||||
Layout.fillWidth: false
|
||||
|
||||
ChatMessageButton {
|
||||
id: thumbsUp
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
opacity: thumbsUpState || thumbsUpState == thumbsDownState ? 1.0 : 0.2
|
||||
source: "qrc:/gpt4all/icons/thumbs_up.svg"
|
||||
name: qsTr("Like response")
|
||||
onClicked: {
|
||||
if (thumbsUpState && !thumbsDownState)
|
||||
return
|
||||
|
||||
chatModel.updateNewResponse(index, "")
|
||||
chatModel.updateThumbsUpState(index, true)
|
||||
chatModel.updateThumbsDownState(index, false)
|
||||
Network.sendConversation(currentChat.id, getConversationJson());
|
||||
}
|
||||
}
|
||||
|
||||
ChatMessageButton {
|
||||
id: thumbsDown
|
||||
anchors.top: thumbsUp.top
|
||||
anchors.topMargin: buttonRow.spacing
|
||||
anchors.left: thumbsUp.right
|
||||
anchors.leftMargin: buttonRow.spacing
|
||||
checked: thumbsDownState
|
||||
opacity: thumbsDownState || thumbsUpState == thumbsDownState ? 1.0 : 0.2
|
||||
bgTransform: [
|
||||
Matrix4x4 {
|
||||
matrix: Qt.matrix4x4(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
|
||||
},
|
||||
Translate {
|
||||
x: thumbsDown.width
|
||||
}
|
||||
]
|
||||
source: "qrc:/gpt4all/icons/thumbs_down.svg"
|
||||
name: qsTr("Dislike response")
|
||||
onClicked: {
|
||||
thumbsDownDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // GridLayout
|
||||
|
||||
} // Item
|
||||
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: parent.width
|
||||
|
||||
function shouldShowSuggestions() {
|
||||
if (!isCurrentResponse)
|
||||
return false;
|
||||
if (MySettings.suggestionMode === 2) // Off
|
||||
return false;
|
||||
if (MySettings.suggestionMode === 0 && consolidatedSources.length === 0) // LocalDocs only
|
||||
return false;
|
||||
return currentChat.responseState === Chat.GeneratingQuestions || currentChat.generatedQuestions.length !== 0;
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: parent.shouldShowSuggestions()
|
||||
Layout.row: 5
|
||||
Layout.column: 0
|
||||
Layout.topMargin: 20
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
Layout.preferredWidth: 28
|
||||
Layout.preferredHeight: 28
|
||||
Image {
|
||||
id: stack
|
||||
sourceSize: Qt.size(28, 28)
|
||||
fillMode: Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
visible: false
|
||||
source: "qrc:/gpt4all/icons/stack.svg"
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: stack
|
||||
source: stack
|
||||
color: theme.conversationHeader
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: parent.shouldShowSuggestions()
|
||||
Layout.row: 5
|
||||
Layout.column: 1
|
||||
Layout.topMargin: 20
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 38
|
||||
RowLayout {
|
||||
spacing: 5
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
TextArea {
|
||||
text: qsTr("Suggested follow-ups")
|
||||
padding: 0
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
font.bold: true
|
||||
color: theme.conversationHeader
|
||||
enabled: false
|
||||
focus: false
|
||||
readOnly: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: parent.shouldShowSuggestions()
|
||||
Layout.row: 6
|
||||
Layout.column: 1
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 1
|
||||
spacing: 10
|
||||
Repeater {
|
||||
model: currentChat.generatedQuestions
|
||||
TextArea {
|
||||
id: followUpText
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
rightPadding: 40
|
||||
topPadding: 10
|
||||
leftPadding: 20
|
||||
bottomPadding: 10
|
||||
text: modelData
|
||||
focus: false
|
||||
readOnly: true
|
||||
wrapMode: Text.WordWrap
|
||||
hoverEnabled: !currentChat.responseInProgress
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
background: Rectangle {
|
||||
color: hovered ? theme.sourcesBackgroundHovered : theme.sourcesBackground
|
||||
radius: 10
|
||||
}
|
||||
MouseArea {
|
||||
id: maFollowUp
|
||||
anchors.fill: parent
|
||||
enabled: !currentChat.responseInProgress
|
||||
onClicked: function() {
|
||||
var chat = window.currentChat
|
||||
var followup = modelData
|
||||
chat.stopGenerating()
|
||||
chat.newPromptResponsePair(followup)
|
||||
}
|
||||
}
|
||||
Item {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 40
|
||||
height: 40
|
||||
visible: !currentChat.responseInProgress
|
||||
Image {
|
||||
id: plusImage
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
sourceSize.width: 20
|
||||
sourceSize.height: 20
|
||||
mipmap: true
|
||||
visible: false
|
||||
source: "qrc:/gpt4all/icons/plus.svg"
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: plusImage
|
||||
source: plusImage
|
||||
color: theme.styledTextColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
color: "transparent"
|
||||
radius: 10
|
||||
Layout.preferredHeight: currentChat.responseInProgress ? 40 : 0
|
||||
clip: true
|
||||
ColumnLayout {
|
||||
id: followUpLayout
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
id: myRect1
|
||||
Layout.preferredWidth: 0
|
||||
Layout.minimumWidth: 0
|
||||
Layout.maximumWidth: parent.width
|
||||
height: 12
|
||||
color: theme.sourcesBackgroundHovered
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: myRect2
|
||||
Layout.preferredWidth: 0
|
||||
Layout.minimumWidth: 0
|
||||
Layout.maximumWidth: parent.width
|
||||
height: 12
|
||||
color: theme.sourcesBackgroundHovered
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: followUpProgressAnimation
|
||||
ParallelAnimation {
|
||||
PropertyAnimation {
|
||||
target: myRect1
|
||||
property: "Layout.preferredWidth"
|
||||
from: 0
|
||||
to: followUpLayout.width
|
||||
duration: 1000
|
||||
}
|
||||
PropertyAnimation {
|
||||
target: myRect2
|
||||
property: "Layout.preferredWidth"
|
||||
from: 0
|
||||
to: followUpLayout.width / 2
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
SequentialAnimation {
|
||||
loops: Animation.Infinite
|
||||
ParallelAnimation {
|
||||
PropertyAnimation {
|
||||
target: myRect1
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0.2
|
||||
duration: 1500
|
||||
}
|
||||
PropertyAnimation {
|
||||
target: myRect2
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0.2
|
||||
duration: 1500
|
||||
}
|
||||
}
|
||||
ParallelAnimation {
|
||||
PropertyAnimation {
|
||||
target: myRect1
|
||||
property: "opacity"
|
||||
from: 0.2
|
||||
to: 1
|
||||
duration: 1500
|
||||
}
|
||||
PropertyAnimation {
|
||||
target: myRect2
|
||||
property: "opacity"
|
||||
from: 0.2
|
||||
to: 1
|
||||
duration: 1500
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible)
|
||||
followUpProgressAnimation.start();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on Layout.preferredHeight {
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // GridLayout
|
||||
|
||||
} // ColumnLayout
|
||||
@@ -1,20 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
import gpt4all
|
||||
|
||||
MyToolButton {
|
||||
property string name
|
||||
|
||||
width: 24
|
||||
height: 24
|
||||
imageWidth: width
|
||||
imageHeight: height
|
||||
ToolTip {
|
||||
visible: parent.hovered
|
||||
y: parent.height * 1.5
|
||||
text: name
|
||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
}
|
||||
Accessible.name: name
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import QtQuick.Layouts
|
||||
|
||||
import gpt4all
|
||||
import mysettings
|
||||
import toolenums
|
||||
|
||||
TextArea {
|
||||
id: myTextArea
|
||||
property string textContent: ""
|
||||
visible: textContent != ""
|
||||
Layout.fillWidth: true
|
||||
padding: 0
|
||||
color: {
|
||||
if (!currentChat.isServer)
|
||||
return theme.textColor
|
||||
return theme.white
|
||||
}
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: TextEdit.PlainText
|
||||
focus: false
|
||||
readOnly: true
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
cursorVisible: isCurrentResponse ? currentChat.responseInProgress : false
|
||||
cursorPosition: text.length
|
||||
TapHandler {
|
||||
id: tapHandler
|
||||
onTapped: function(eventPoint, button) {
|
||||
var clickedPos = myTextArea.positionAt(eventPoint.position.x, eventPoint.position.y);
|
||||
var success = textProcessor.tryCopyAtPosition(clickedPos);
|
||||
if (success)
|
||||
copyCodeMessage.open();
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: conversationMouseArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
conversationContextMenu.x = conversationMouseArea.mouseX
|
||||
conversationContextMenu.y = conversationMouseArea.mouseY
|
||||
conversationContextMenu.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onLinkActivated: function(link) {
|
||||
if (!isCurrentResponse || !currentChat.responseInProgress)
|
||||
Qt.openUrlExternally(link)
|
||||
}
|
||||
|
||||
onLinkHovered: function (link) {
|
||||
if (!isCurrentResponse || !currentChat.responseInProgress)
|
||||
statusBar.externalHoveredLink = link
|
||||
}
|
||||
|
||||
MyMenu {
|
||||
id: conversationContextMenu
|
||||
MyMenuItem {
|
||||
text: qsTr("Copy")
|
||||
enabled: myTextArea.selectedText !== ""
|
||||
height: enabled ? implicitHeight : 0
|
||||
onTriggered: myTextArea.copy()
|
||||
}
|
||||
MyMenuItem {
|
||||
text: qsTr("Copy Message")
|
||||
enabled: myTextArea.selectedText === ""
|
||||
height: enabled ? implicitHeight : 0
|
||||
onTriggered: {
|
||||
myTextArea.selectAll()
|
||||
myTextArea.copy()
|
||||
myTextArea.deselect()
|
||||
}
|
||||
}
|
||||
MyMenuItem {
|
||||
text: textProcessor.shouldProcessText ? qsTr("Disable markdown") : qsTr("Enable markdown")
|
||||
height: enabled ? implicitHeight : 0
|
||||
onTriggered: {
|
||||
textProcessor.shouldProcessText = !textProcessor.shouldProcessText;
|
||||
textProcessor.setValue(textContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChatViewTextProcessor {
|
||||
id: textProcessor
|
||||
}
|
||||
|
||||
function resetChatViewTextProcessor() {
|
||||
textProcessor.fontPixelSize = myTextArea.font.pixelSize
|
||||
textProcessor.codeColors.defaultColor = theme.codeDefaultColor
|
||||
textProcessor.codeColors.keywordColor = theme.codeKeywordColor
|
||||
textProcessor.codeColors.functionColor = theme.codeFunctionColor
|
||||
textProcessor.codeColors.functionCallColor = theme.codeFunctionCallColor
|
||||
textProcessor.codeColors.commentColor = theme.codeCommentColor
|
||||
textProcessor.codeColors.stringColor = theme.codeStringColor
|
||||
textProcessor.codeColors.numberColor = theme.codeNumberColor
|
||||
textProcessor.codeColors.headerColor = theme.codeHeaderColor
|
||||
textProcessor.codeColors.backgroundColor = theme.codeBackgroundColor
|
||||
textProcessor.textDocument = textDocument
|
||||
textProcessor.setValue(textContent);
|
||||
}
|
||||
|
||||
property bool textProcessorReady: false
|
||||
|
||||
Component.onCompleted: {
|
||||
resetChatViewTextProcessor();
|
||||
textProcessorReady = true;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: myTextArea
|
||||
function onTextContentChanged() {
|
||||
if (myTextArea.textProcessorReady)
|
||||
textProcessor.setValue(textContent);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: MySettings
|
||||
function onFontSizeChanged() {
|
||||
myTextArea.resetChatViewTextProcessor();
|
||||
}
|
||||
function onChatThemeChanged() {
|
||||
myTextArea.resetChatViewTextProcessor();
|
||||
}
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.Paragraph
|
||||
Accessible.name: text
|
||||
Accessible.description: name === "Response: " ? "The response by the model" : "The prompt by the user"
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import QtQuick.Layouts
|
||||
|
||||
MyDialog {
|
||||
id: confirmationDialog
|
||||
anchors.centerIn: parent
|
||||
modal: true
|
||||
padding: 20
|
||||
property alias dialogTitle: titleText.text
|
||||
property alias description: descriptionText.text
|
||||
|
||||
Theme { id: theme }
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
Text {
|
||||
id: titleText
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
textFormat: Text.StyledText
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Text {
|
||||
id: descriptionText
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
textFormat: Text.StyledText
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeMedium
|
||||
}
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
id: dialogBox
|
||||
padding: 20
|
||||
alignment: Qt.AlignRight
|
||||
spacing: 10
|
||||
MySettingsButton {
|
||||
text: qsTr("OK")
|
||||
textColor: theme.mediumButtonText
|
||||
backgroundColor: theme.mediumButtonBackground
|
||||
backgroundColorHovered: theme.mediumButtonBackgroundHovered
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
|
||||
}
|
||||
MySettingsButton {
|
||||
text: qsTr("Cancel")
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
|
||||
}
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
Keys.onEnterPressed: confirmationDialog.accept()
|
||||
Keys.onReturnPressed: confirmationDialog.accept()
|
||||
}
|
||||
Component.onCompleted: dialogBox.forceActiveFocus()
|
||||
}
|
||||
@@ -47,7 +47,7 @@ Rectangle {
|
||||
id: welcome
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("Welcome to GPT4All")
|
||||
font.pixelSize: theme.fontSizeBannerLarge
|
||||
font.pixelSize: theme.fontSizeBanner
|
||||
color: theme.titleTextColor
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ Rectangle {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
text: qsTr("Subscribe to Newsletter")
|
||||
imageSource: "qrc:/gpt4all/icons/email.svg"
|
||||
onClicked: { Qt.openUrlExternally("https://nomic.ai/gpt4all/#newsletter-form") }
|
||||
onClicked: { Qt.openUrlExternally("https://forms.nomic.ai/gpt4all-release-notes-signup") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import mysettings
|
||||
import network
|
||||
|
||||
MySettingsTab {
|
||||
onRestoreDefaults: {
|
||||
onRestoreDefaultsClicked: {
|
||||
MySettings.restoreLocalDocsDefaults();
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ MySettingsTab {
|
||||
/* 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", "ppt", "pptx", "xls", "xlsx",
|
||||
/* 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",
|
||||
@@ -176,7 +176,6 @@ MySettingsTab {
|
||||
ListElement { text: qsTr("Application default") }
|
||||
Component.onCompleted: {
|
||||
MySettings.embeddingsDeviceList.forEach(d => append({"text": d}));
|
||||
deviceBox.updateModel();
|
||||
}
|
||||
}
|
||||
Accessible.name: deviceLabel.text
|
||||
|
||||
@@ -8,34 +8,10 @@ import mysettings
|
||||
import chatlistmodel
|
||||
|
||||
MySettingsTab {
|
||||
onRestoreDefaults: {
|
||||
onRestoreDefaultsClicked: {
|
||||
MySettings.restoreModelDefaults(root.currentModelInfo);
|
||||
}
|
||||
title: qsTr("Model")
|
||||
|
||||
ConfirmationDialog {
|
||||
id: resetSystemMessageDialog
|
||||
property var index: null
|
||||
property bool resetClears: false
|
||||
dialogTitle: qsTr("%1 system message?").arg(resetClears ? qsTr("Clear") : qsTr("Reset"))
|
||||
description: qsTr("The system message will be %1.").arg(resetClears ? qsTr("removed") : qsTr("reset to the default"))
|
||||
onAccepted: MySettings.resetModelSystemMessage(ModelList.modelInfo(index))
|
||||
function show(index_, resetClears_) { index = index_; resetClears = resetClears_; open(); }
|
||||
}
|
||||
|
||||
ConfirmationDialog {
|
||||
id: resetChatTemplateDialog
|
||||
property bool resetClears: false
|
||||
property var index: null
|
||||
dialogTitle: qsTr("%1 chat template?").arg(resetClears ? qsTr("Clear") : qsTr("Reset"))
|
||||
description: qsTr("The chat template will be %1.").arg(resetClears ? qsTr("erased") : qsTr("reset to the default"))
|
||||
onAccepted: {
|
||||
MySettings.resetModelChatTemplate(ModelList.modelInfo(index));
|
||||
templateTextArea.resetText();
|
||||
}
|
||||
function show(index_, resetClears_) { index = index_; resetClears = resetClears_; open(); }
|
||||
}
|
||||
|
||||
contentItem: GridLayout {
|
||||
id: root
|
||||
columns: 3
|
||||
@@ -59,7 +35,6 @@ MySettingsTab {
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.row: 2
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 2
|
||||
@@ -178,154 +153,69 @@ MySettingsTab {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
MySettingsLabel {
|
||||
visible: !root.currentModelInfo.isOnline
|
||||
text: qsTr("System Prompt")
|
||||
helpText: qsTr("Prefixed at the beginning of every conversation. Must contain the appropriate framing tokens.")
|
||||
Layout.row: 7
|
||||
Layout.columnSpan: 2
|
||||
Layout.column: 0
|
||||
Layout.topMargin: 15
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: parent.width
|
||||
spacing: 10
|
||||
MySettingsLabel {
|
||||
id: systemMessageLabel
|
||||
text: qsTr("System Message")
|
||||
helpText: qsTr("A message to set the context or guide the behavior of the model. Leave blank for " +
|
||||
"none. NOTE: Since GPT4All 3.5, this should not contain control tokens.")
|
||||
onReset: () => resetSystemMessageDialog.show(root.currentModelId, resetClears)
|
||||
function updateResetButton() {
|
||||
const info = root.currentModelInfo;
|
||||
// NOTE: checks if the *override* is set, regardless of whether there is a default
|
||||
canReset = !!info.id && MySettings.isModelSystemMessageSet(info);
|
||||
resetClears = !info.defaultSystemMessage;
|
||||
}
|
||||
Component.onCompleted: updateResetButton()
|
||||
Connections {
|
||||
target: root
|
||||
function onCurrentModelIdChanged() { systemMessageLabel.updateResetButton(); }
|
||||
}
|
||||
Connections {
|
||||
target: MySettings
|
||||
function onSystemMessageChanged(info)
|
||||
{ if (info.id === root.currentModelId) systemMessageLabel.updateResetButton(); }
|
||||
}
|
||||
}
|
||||
Label {
|
||||
id: systemMessageLabelHelp
|
||||
visible: systemMessageArea.errState !== "ok"
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 5
|
||||
Layout.maximumHeight: systemMessageLabel.height
|
||||
text: qsTr("System message is not " +
|
||||
"<a href=\"https://docs.gpt4all.io/gpt4all_desktop/chat_templates.html\">plain text</a>.")
|
||||
color: systemMessageArea.errState === "error" ? theme.textErrorColor : theme.textWarningColor
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
font.bold: true
|
||||
wrapMode: Text.Wrap
|
||||
elide: Text.ElideRight
|
||||
onLinkActivated: function(link) { Qt.openUrlExternally(link) }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: systemMessage
|
||||
id: systemPrompt
|
||||
visible: !root.currentModelInfo.isOnline
|
||||
Layout.row: 8
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
color: "transparent"
|
||||
Layout.minimumHeight: Math.max(100, systemMessageArea.contentHeight + 20)
|
||||
Layout.minimumHeight: Math.max(100, systemPromptArea.contentHeight + 20)
|
||||
MyTextArea {
|
||||
id: systemMessageArea
|
||||
id: systemPromptArea
|
||||
anchors.fill: parent
|
||||
property bool isBeingReset: false
|
||||
function resetText() {
|
||||
const info = root.currentModelInfo;
|
||||
isBeingReset = true;
|
||||
text = (info.id ? info.systemMessage.value : null) ?? "";
|
||||
isBeingReset = false;
|
||||
}
|
||||
Component.onCompleted: resetText()
|
||||
text: root.currentModelInfo.systemPrompt
|
||||
Connections {
|
||||
target: MySettings
|
||||
function onSystemMessageChanged(info)
|
||||
{ if (info.id === root.currentModelId) systemMessageArea.resetText(); }
|
||||
function onSystemPromptChanged() {
|
||||
systemPromptArea.text = root.currentModelInfo.systemPrompt;
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: root
|
||||
function onCurrentModelIdChanged() { systemMessageArea.resetText(); }
|
||||
function onCurrentModelInfoChanged() {
|
||||
systemPromptArea.text = root.currentModelInfo.systemPrompt;
|
||||
}
|
||||
}
|
||||
// strict validation, because setModelSystemMessage clears isLegacy
|
||||
readonly property var reLegacyCheck: (
|
||||
/(?:^|\s)(?:### *System\b|S(?:ystem|YSTEM):)|<\|(?:im_(?:start|end)|(?:start|end)_header_id|eot_id|SYSTEM_TOKEN)\|>|<<SYS>>/m
|
||||
)
|
||||
onTextChanged: {
|
||||
const info = root.currentModelInfo;
|
||||
if (!info.id) {
|
||||
errState = "ok";
|
||||
} else if (info.systemMessage.isLegacy && (isBeingReset || reLegacyCheck.test(text))) {
|
||||
errState = "error";
|
||||
} else
|
||||
errState = reLegacyCheck.test(text) ? "warning" : "ok";
|
||||
if (info.id && errState !== "error" && !isBeingReset)
|
||||
MySettings.setModelSystemMessage(info, text);
|
||||
systemMessageLabel.updateResetButton();
|
||||
MySettings.setModelSystemPrompt(root.currentModelInfo, text)
|
||||
}
|
||||
Accessible.role: Accessible.EditableText
|
||||
Accessible.name: systemMessageLabel.text
|
||||
Accessible.description: systemMessageLabelHelp.text
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.row: 9
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 2
|
||||
Layout.topMargin: 15
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: parent.width
|
||||
spacing: 10
|
||||
MySettingsLabel {
|
||||
id: chatTemplateLabel
|
||||
text: qsTr("Chat Template")
|
||||
helpText: qsTr("This Jinja template turns the chat into input for the model.")
|
||||
onReset: () => resetChatTemplateDialog.show(root.currentModelId, resetClears)
|
||||
function updateResetButton() {
|
||||
const info = root.currentModelInfo;
|
||||
canReset = !!info.id && (
|
||||
MySettings.isModelChatTemplateSet(info)
|
||||
|| templateTextArea.text !== (info.chatTemplate.value ?? "")
|
||||
);
|
||||
resetClears = !info.defaultChatTemplate;
|
||||
}
|
||||
Component.onCompleted: updateResetButton()
|
||||
Connections {
|
||||
target: root
|
||||
function onCurrentModelIdChanged() { chatTemplateLabel.updateResetButton(); }
|
||||
}
|
||||
Connections {
|
||||
target: MySettings
|
||||
function onChatTemplateChanged(info)
|
||||
{ if (info.id === root.currentModelId) chatTemplateLabel.updateResetButton(); }
|
||||
}
|
||||
id: promptTemplateLabel
|
||||
text: qsTr("Prompt Template")
|
||||
helpText: qsTr("The template that wraps every prompt.")
|
||||
}
|
||||
Label {
|
||||
id: chatTemplateLabelHelp
|
||||
visible: templateTextArea.errState !== "ok"
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 5
|
||||
Layout.maximumHeight: chatTemplateLabel.height
|
||||
text: templateTextArea.errMsg
|
||||
color: templateTextArea.errState === "error" ? theme.textErrorColor : theme.textWarningColor
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
font.bold: true
|
||||
wrapMode: Text.Wrap
|
||||
elide: Text.ElideRight
|
||||
onLinkActivated: function(link) { Qt.openUrlExternally(link) }
|
||||
MySettingsLabel {
|
||||
id: promptTemplateLabelHelp
|
||||
text: qsTr("Must contain the string \"%1\" to be replaced with the user's input.")
|
||||
color: theme.textErrorColor
|
||||
visible: templateTextArea.text.indexOf("%1") === -1
|
||||
wrapMode: TextArea.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: chatTemplate
|
||||
id: promptTemplate
|
||||
Layout.row: 10
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 2
|
||||
@@ -336,71 +226,27 @@ MySettingsTab {
|
||||
MyTextArea {
|
||||
id: templateTextArea
|
||||
anchors.fill: parent
|
||||
font: fixedFont
|
||||
property bool isBeingReset: false
|
||||
property var errMsg: null
|
||||
function resetText() {
|
||||
const info = root.currentModelInfo;
|
||||
isBeingReset = true;
|
||||
text = (info.id ? info.chatTemplate.value : null) ?? "";
|
||||
isBeingReset = false;
|
||||
}
|
||||
Component.onCompleted: resetText()
|
||||
text: root.currentModelInfo.promptTemplate
|
||||
Connections {
|
||||
target: MySettings
|
||||
function onChatTemplateChanged() { templateTextArea.resetText(); }
|
||||
function onPromptTemplateChanged() {
|
||||
templateTextArea.text = root.currentModelInfo.promptTemplate;
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: root
|
||||
function onCurrentModelIdChanged() { templateTextArea.resetText(); }
|
||||
}
|
||||
function legacyCheck() {
|
||||
return /%[12]\b/.test(text) || !/\{%.*%\}.*\{\{.*\}\}.*\{%.*%\}/.test(text.replace(/\n/g, ''))
|
||||
|| !/\bcontent\b/.test(text);
|
||||
function onCurrentModelInfoChanged() {
|
||||
templateTextArea.text = root.currentModelInfo.promptTemplate;
|
||||
}
|
||||
}
|
||||
onTextChanged: {
|
||||
const info = root.currentModelInfo;
|
||||
let jinjaError;
|
||||
if (!info.id) {
|
||||
errMsg = null;
|
||||
errState = "ok";
|
||||
} else if (info.chatTemplate.isLegacy && (isBeingReset || legacyCheck())) {
|
||||
errMsg = null;
|
||||
errState = "error";
|
||||
} else if (text === "" && !info.chatTemplate.isSet) {
|
||||
errMsg = qsTr("No <a href=\"https://docs.gpt4all.io/gpt4all_desktop/chat_templates.html\">" +
|
||||
"chat template</a> configured.");
|
||||
errState = "error";
|
||||
} else if (/^\s*$/.test(text)) {
|
||||
errMsg = qsTr("The <a href=\"https://docs.gpt4all.io/gpt4all_desktop/chat_templates.html\">" +
|
||||
"chat template</a> cannot be blank.");
|
||||
errState = "error";
|
||||
} else if ((jinjaError = MySettings.checkJinjaTemplateError(text)) !== null) {
|
||||
errMsg = qsTr("<a href=\"https://docs.gpt4all.io/gpt4all_desktop/chat_templates.html\">Syntax" +
|
||||
" error</a>: %1").arg(jinjaError);
|
||||
errState = "error";
|
||||
} else if (legacyCheck()) {
|
||||
errMsg = qsTr("Chat template is not in " +
|
||||
"<a href=\"https://docs.gpt4all.io/gpt4all_desktop/chat_templates.html\">" +
|
||||
"Jinja format</a>.")
|
||||
errState = "warning";
|
||||
} else {
|
||||
errState = "ok";
|
||||
}
|
||||
if (info.id && errState !== "error" && !isBeingReset)
|
||||
MySettings.setModelChatTemplate(info, text);
|
||||
chatTemplateLabel.updateResetButton();
|
||||
}
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Tab) {
|
||||
const a = templateTextArea;
|
||||
event.accepted = true; // suppress tab
|
||||
a.insert(a.cursorPosition, ' '); // four spaces
|
||||
if (templateTextArea.text.indexOf("%1") !== -1) {
|
||||
MySettings.setModelPromptTemplate(root.currentModelInfo, text)
|
||||
}
|
||||
}
|
||||
Accessible.role: Accessible.EditableText
|
||||
Accessible.name: chatTemplateLabel.text
|
||||
Accessible.description: chatTemplateLabelHelp.text
|
||||
Accessible.name: promptTemplateLabel.text
|
||||
Accessible.description: promptTemplateLabelHelp.text
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Dialogs
|
||||
|
||||
FileDialog {
|
||||
id: fileDialog
|
||||
title: qsTr("Please choose a file")
|
||||
property var acceptedConnection: null
|
||||
|
||||
function openFileDialog(currentFolder, onAccepted) {
|
||||
fileDialog.currentFolder = currentFolder;
|
||||
if (acceptedConnection !== null) {
|
||||
fileDialog.accepted.disconnect(acceptedConnection);
|
||||
}
|
||||
acceptedConnection = function() { onAccepted(fileDialog.selectedFile); };
|
||||
fileDialog.accepted.connect(acceptedConnection);
|
||||
fileDialog.open();
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
Item {
|
||||
id: fileIcon
|
||||
property real iconSize: 24
|
||||
property string fileName: ""
|
||||
implicitWidth: iconSize
|
||||
implicitHeight: iconSize
|
||||
|
||||
Image {
|
||||
id: fileImage
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
sourceSize.width: iconSize
|
||||
sourceSize.height: iconSize
|
||||
mipmap: true
|
||||
source: {
|
||||
if (fileIcon.fileName.toLowerCase().endsWith(".txt"))
|
||||
return "qrc:/gpt4all/icons/file-txt.svg"
|
||||
else if (fileIcon.fileName.toLowerCase().endsWith(".pdf"))
|
||||
return "qrc:/gpt4all/icons/file-pdf.svg"
|
||||
else if (fileIcon.fileName.toLowerCase().endsWith(".md"))
|
||||
return "qrc:/gpt4all/icons/file-md.svg"
|
||||
else if (fileIcon.fileName.toLowerCase().endsWith(".xlsx"))
|
||||
return "qrc:/gpt4all/icons/file-xls.svg"
|
||||
else if (fileIcon.fileName.toLowerCase().endsWith(".docx"))
|
||||
return "qrc:/gpt4all/icons/file-docx.svg"
|
||||
else
|
||||
return "qrc:/gpt4all/icons/file.svg"
|
||||
}
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: fileImage
|
||||
source: fileImage
|
||||
color: theme.textColor
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Dialogs
|
||||
|
||||
FolderDialog {
|
||||
id: folderDialog
|
||||
title: qsTr("Please choose a directory")
|
||||
|
||||
function openFolderDialog(currentFolder, onAccepted) {
|
||||
folderDialog.currentFolder = currentFolder;
|
||||
folderDialog.accepted.connect(function() { onAccepted(folderDialog.selectedFolder); });
|
||||
folderDialog.open();
|
||||
}
|
||||
}
|
||||
@@ -22,30 +22,12 @@ Menu {
|
||||
|
||||
contentItem: Rectangle {
|
||||
implicitWidth: myListView.contentWidth
|
||||
implicitHeight: (myTitle.visible ? myTitle.contentHeight + 10: 0) + myListView.contentHeight
|
||||
implicitHeight: myListView.contentHeight
|
||||
color: "transparent"
|
||||
|
||||
Text {
|
||||
id: myTitle
|
||||
visible: menu.title !== ""
|
||||
text: menu.title
|
||||
anchors.margins: 10
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
leftPadding: 15
|
||||
rightPadding: 10
|
||||
padding: 5
|
||||
color: theme.styledTextColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
}
|
||||
ListView {
|
||||
id: myListView
|
||||
anchors.margins: 10
|
||||
anchors.top: title.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.fill: parent
|
||||
implicitHeight: contentHeight
|
||||
model: menu.contentModel
|
||||
interactive: Window.window
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import QtQuick.Layouts
|
||||
|
||||
MenuItem {
|
||||
id: item
|
||||
@@ -13,40 +11,12 @@ MenuItem {
|
||||
color: item.highlighted ? theme.menuHighlightColor : theme.menuBackgroundColor
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: 0
|
||||
Item {
|
||||
visible: item.icon.source.toString() !== ""
|
||||
Layout.leftMargin: 6
|
||||
Layout.preferredWidth: item.icon.width
|
||||
Layout.preferredHeight: item.icon.height
|
||||
Image {
|
||||
id: image
|
||||
anchors.centerIn: parent
|
||||
visible: false
|
||||
fillMode: Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
sourceSize.width: item.icon.width
|
||||
sourceSize.height: item.icon.height
|
||||
source: item.icon.source
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: image
|
||||
source: image
|
||||
color: theme.textColor
|
||||
}
|
||||
}
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
padding: 5
|
||||
text: item.text
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
}
|
||||
Rectangle {
|
||||
color: "transparent"
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
}
|
||||
contentItem: Text {
|
||||
leftPadding: 10
|
||||
rightPadding: 10
|
||||
padding: 5
|
||||
text: item.text
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ Button {
|
||||
property color borderColor: "transparent"
|
||||
property real fontPixelSize: theme.fontSizeLarge
|
||||
property string toolTip
|
||||
property alias backgroundRadius: background.radius
|
||||
|
||||
contentItem: Text {
|
||||
text: myButton.text
|
||||
@@ -29,7 +28,6 @@ Button {
|
||||
Accessible.name: text
|
||||
}
|
||||
background: Rectangle {
|
||||
id: background
|
||||
radius: 10
|
||||
border.width: borderWidth
|
||||
border.color: borderColor
|
||||
|
||||
@@ -17,42 +17,13 @@ ColumnLayout {
|
||||
property alias color: mainTextLabel.color
|
||||
property alias linkColor: mainTextLabel.linkColor
|
||||
|
||||
property var onReset: null
|
||||
property alias canReset: resetButton.enabled
|
||||
property bool resetClears: false
|
||||
|
||||
Item {
|
||||
anchors.margins: 5
|
||||
width: childrenRect.width
|
||||
height: mainTextLabel.contentHeight
|
||||
|
||||
Label {
|
||||
id: mainTextLabel
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
color: theme.settingsTitleTextColor
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
font.bold: true
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
onLinkActivated: function(link) {
|
||||
root.linkActivated(link);
|
||||
}
|
||||
}
|
||||
|
||||
MySettingsButton {
|
||||
id: resetButton
|
||||
anchors.baseline: mainTextLabel.baseline
|
||||
anchors.left: mainTextLabel.right
|
||||
height: mainTextLabel.contentHeight
|
||||
anchors.leftMargin: 10
|
||||
padding: 2
|
||||
leftPadding: 10
|
||||
rightPadding: 10
|
||||
backgroundRadius: 5
|
||||
text: resetClears ? qsTr("Clear") : qsTr("Reset")
|
||||
visible: root.onReset !== null
|
||||
onClicked: root.onReset()
|
||||
Label {
|
||||
id: mainTextLabel
|
||||
color: theme.settingsTitleTextColor
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
font.bold: true
|
||||
onLinkActivated: function(link) {
|
||||
root.linkActivated(link);
|
||||
}
|
||||
}
|
||||
Label {
|
||||
|
||||
@@ -2,7 +2,6 @@ import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import QtQuick.Controls.impl
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
import Qt.labs.folderlistmodel
|
||||
@@ -62,6 +61,17 @@ Item {
|
||||
color: theme.settingsDivider
|
||||
}
|
||||
|
||||
FolderDialog {
|
||||
id: folderDialog
|
||||
title: qsTr("Please choose a directory")
|
||||
}
|
||||
|
||||
function openFolderDialog(currentFolder, onAccepted) {
|
||||
folderDialog.currentFolder = currentFolder;
|
||||
folderDialog.accepted.connect(function() { onAccepted(folderDialog.selectedFolder); });
|
||||
folderDialog.open();
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
id: stackLayout
|
||||
anchors.top: tabTitlesModel.count > 1 ? dividerTabBar.bottom : parent.top
|
||||
@@ -78,6 +88,7 @@ Item {
|
||||
sourceComponent: model.modelData
|
||||
onLoaded: {
|
||||
settingsStack.tabTitlesModel.append({ "title": loader.item.title });
|
||||
item.openFolderDialog = settingsStack.openFolderDialog;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ Item {
|
||||
property string title: ""
|
||||
property Item contentItem: null
|
||||
property bool showRestoreDefaultsButton: true
|
||||
signal restoreDefaults
|
||||
property var openFolderDialog
|
||||
signal restoreDefaultsClicked
|
||||
|
||||
onContentItemChanged: function() {
|
||||
if (contentItem) {
|
||||
@@ -19,13 +20,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmationDialog {
|
||||
id: restoreDefaultsDialog
|
||||
dialogTitle: qsTr("Restore defaults?")
|
||||
description: qsTr("This page of settings will be reset to the defaults.")
|
||||
onAccepted: root.restoreDefaults()
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
width: parent.width
|
||||
@@ -54,7 +48,6 @@ Item {
|
||||
Column {
|
||||
id: contentInner
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: parent.width
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -71,7 +64,9 @@ Item {
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: text
|
||||
Accessible.description: qsTr("Restores settings dialog to a default state")
|
||||
onClicked: restoreDefaultsDialog.open()
|
||||
onClicked: {
|
||||
root.restoreDefaultsClicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import mysettings
|
||||
import mysettingsenums
|
||||
|
||||
MySettingsButton {
|
||||
property bool isSelected: false
|
||||
contentItem: Text {
|
||||
text: parent.text
|
||||
horizontalAlignment: Qt.AlignCenter
|
||||
color: isSelected ? theme.titleTextColor : theme.styledTextColor
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
}
|
||||
background: Item {
|
||||
visible: isSelected || hovered
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 3
|
||||
color: isSelected ? theme.titleTextColor : theme.styledTextColorLighter
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,27 +5,18 @@ import QtQuick.Controls.Basic
|
||||
|
||||
TextArea {
|
||||
id: myTextArea
|
||||
|
||||
property string errState: "ok" // one of "ok", "error", "warning"
|
||||
|
||||
color: enabled ? theme.textColor : theme.mutedTextColor
|
||||
placeholderTextColor: theme.mutedTextColor
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
background: Rectangle {
|
||||
implicitWidth: 150
|
||||
color: theme.controlBackground
|
||||
border.width: errState === "ok" ? 1 : 2
|
||||
border.color: {
|
||||
switch (errState) {
|
||||
case "ok": return theme.controlBorder;
|
||||
case "warning": return theme.textWarningColor;
|
||||
case "error": return theme.textErrorColor;
|
||||
}
|
||||
}
|
||||
border.width: 1
|
||||
border.color: theme.controlBorder
|
||||
radius: 10
|
||||
}
|
||||
padding: 10
|
||||
wrapMode: TextArea.Wrap
|
||||
|
||||
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ Button {
|
||||
property alias fillMode: image.fillMode
|
||||
property alias imageWidth: image.sourceSize.width
|
||||
property alias imageHeight: image.sourceSize.height
|
||||
property alias bgTransform: background.transform
|
||||
contentItem: Text {
|
||||
text: myButton.text
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -27,7 +26,6 @@ Button {
|
||||
}
|
||||
|
||||
background: Item {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
@@ -49,7 +47,7 @@ Button {
|
||||
ColorOverlay {
|
||||
anchors.fill: image
|
||||
source: image
|
||||
color: !myButton.enabled ? theme.mutedTextColor : myButton.hovered ? backgroundColorHovered : backgroundColor
|
||||
color: myButton.hovered ? backgroundColorHovered : backgroundColor
|
||||
}
|
||||
}
|
||||
Accessible.role: Accessible.Button
|
||||
|
||||
@@ -115,7 +115,7 @@ model release that uses your data!")
|
||||
anchors.right: parent.right
|
||||
Label {
|
||||
id: optInStatistics
|
||||
text: qsTr("Opt-in to anonymous usage analytics used to improve GPT4All")
|
||||
text: "Opt-in to anonymous usage analytics used to improve GPT4All"
|
||||
Layout.row: 0
|
||||
Layout.column: 0
|
||||
color: theme.textColor
|
||||
@@ -229,7 +229,7 @@ model release that uses your data!")
|
||||
|
||||
Label {
|
||||
id: optInNetwork
|
||||
text: qsTr("Opt-in to anonymous sharing of chats to the GPT4All Datalake")
|
||||
text: "Opt-in to anonymous sharing of chats to the GPT4All Datalake"
|
||||
Layout.row: 1
|
||||
Layout.column: 0
|
||||
color: theme.textColor
|
||||
|
||||
46
gpt4all-chat/qml/SwitchModelDialog.qml
Normal file
@@ -0,0 +1,46 @@
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import QtQuick.Layouts
|
||||
import llm
|
||||
import mysettings
|
||||
|
||||
MyDialog {
|
||||
id: switchModelDialog
|
||||
anchors.centerIn: parent
|
||||
modal: true
|
||||
padding: 20
|
||||
property int index: -1
|
||||
|
||||
Theme {
|
||||
id: theme
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
textFormat: Text.StyledText
|
||||
text: qsTr("<b>Warning:</b> changing the model will erase the current conversation. Do you wish to continue?")
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
id: dialogBox
|
||||
padding: 20
|
||||
alignment: Qt.AlignRight
|
||||
spacing: 10
|
||||
MySettingsButton {
|
||||
text: qsTr("Continue")
|
||||
Accessible.description: qsTr("Continue with model loading")
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
|
||||
}
|
||||
MySettingsButton {
|
||||
text: qsTr("Cancel")
|
||||
Accessible.description: qsTr("Cancel")
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
|
||||
}
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,9 +64,6 @@ QtObject {
|
||||
property color green800: Qt.hsla(123/360, 0.17, 0.24)
|
||||
property color green900: Qt.hsla(124/360, 0.17, 0.20)
|
||||
property color green950: Qt.hsla(125/360, 0.22, 0.10)
|
||||
property color green300_sat: Qt.hsla(122/360, 0.24, 0.73)
|
||||
property color green400_sat: Qt.hsla(122/360, 0.23, 0.58)
|
||||
property color green450_sat: Qt.hsla(122/360, 0.23, 0.52)
|
||||
|
||||
// yellow
|
||||
property color yellow0: Qt.hsla(47/360, 0.90, 0.99)
|
||||
@@ -102,7 +99,6 @@ QtObject {
|
||||
property color purple200: Qt.hsla(279/360, 1.0, 0.91)
|
||||
property color purple300: Qt.hsla(279/360, 1.0, 0.84)
|
||||
property color purple400: Qt.hsla(279/360, 1.0, 0.73)
|
||||
property color purple450: Qt.hsla(279/360, 1.0, 0.68)
|
||||
property color purple500: Qt.hsla(279/360, 1.0, 0.63)
|
||||
property color purple600: Qt.hsla(279/360, 1.0, 0.53)
|
||||
property color purple700: Qt.hsla(279/360, 1.0, 0.47)
|
||||
@@ -181,17 +177,6 @@ QtObject {
|
||||
}
|
||||
}
|
||||
|
||||
property color attachmentBackground: {
|
||||
switch (MySettings.chatTheme) {
|
||||
case MySettingsEnums.ChatTheme.LegacyDark:
|
||||
return blue900
|
||||
case MySettingsEnums.ChatTheme.Dark:
|
||||
return darkgray200
|
||||
default:
|
||||
return gray0
|
||||
}
|
||||
}
|
||||
|
||||
property color disabledControlBackground: {
|
||||
switch (MySettings.chatTheme) {
|
||||
case MySettingsEnums.ChatTheme.LegacyDark:
|
||||
@@ -412,39 +397,6 @@ QtObject {
|
||||
}
|
||||
}
|
||||
|
||||
property color mediumButtonBackground: {
|
||||
switch (MySettings.chatTheme) {
|
||||
case MySettingsEnums.ChatTheme.LegacyDark:
|
||||
return purple400
|
||||
case MySettingsEnums.ChatTheme.Dark:
|
||||
return green400_sat
|
||||
default:
|
||||
return green400_sat
|
||||
}
|
||||
}
|
||||
|
||||
property color mediumButtonBackgroundHovered: {
|
||||
switch (MySettings.chatTheme) {
|
||||
case MySettingsEnums.ChatTheme.LegacyDark:
|
||||
return purple450
|
||||
case MySettingsEnums.ChatTheme.Dark:
|
||||
return green450_sat
|
||||
default:
|
||||
return green300_sat
|
||||
}
|
||||
}
|
||||
|
||||
property color mediumButtonText: {
|
||||
switch (MySettings.chatTheme) {
|
||||
case MySettingsEnums.ChatTheme.LegacyDark:
|
||||
return textColor
|
||||
case MySettingsEnums.ChatTheme.Dark:
|
||||
return textColor
|
||||
default:
|
||||
return white
|
||||
}
|
||||
}
|
||||
|
||||
property color darkButtonText: {
|
||||
switch (MySettings.chatTheme) {
|
||||
case MySettingsEnums.ChatTheme.LegacyDark:
|
||||
@@ -959,8 +911,16 @@ QtObject {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property color textErrorColor: red400
|
||||
readonly property color textWarningColor: yellow400
|
||||
property color textErrorColor: {
|
||||
switch (MySettings.chatTheme) {
|
||||
case MySettingsEnums.ChatTheme.LegacyDark:
|
||||
return red400
|
||||
case MySettingsEnums.ChatTheme.Dark:
|
||||
return red400
|
||||
default:
|
||||
return red400
|
||||
}
|
||||
}
|
||||
|
||||
property color settingsTitleTextColor: {
|
||||
switch (MySettings.chatTheme) {
|
||||
@@ -1017,17 +977,6 @@ QtObject {
|
||||
}
|
||||
}
|
||||
|
||||
property color styledTextColorLighter: {
|
||||
switch (MySettings.chatTheme) {
|
||||
case MySettingsEnums.ChatTheme.LegacyDark:
|
||||
return purple50
|
||||
case MySettingsEnums.ChatTheme.Dark:
|
||||
return yellow0
|
||||
default:
|
||||
return grayRed400
|
||||
}
|
||||
}
|
||||
|
||||
property color styledTextColor2: {
|
||||
switch (MySettings.chatTheme) {
|
||||
case MySettingsEnums.ChatTheme.LegacyDark:
|
||||
@@ -1267,6 +1216,5 @@ QtObject {
|
||||
property real fontSizeLarger: 14 * fontScale
|
||||
property real fontSizeLargest: 18 * fontScale
|
||||
property real fontSizeBannerSmall: 24 * fontScale**.8
|
||||
property real fontSizeBanner: 32 * fontScale**.8
|
||||
property real fontSizeBannerLarge: 48 * fontScale**.8
|
||||
property real fontSizeBanner: 48 * fontScale**.8
|
||||
}
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
#include "chat.h"
|
||||
|
||||
#include "chatlistmodel.h"
|
||||
#include "mysettings.h"
|
||||
#include "network.h"
|
||||
#include "server.h"
|
||||
#include "tool.h"
|
||||
#include "toolcallparser.h"
|
||||
#include "toolmodel.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QDataStream>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QLatin1String>
|
||||
#include <QMap>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <QStringList>
|
||||
#include <QTextStream>
|
||||
#include <Qt>
|
||||
#include <QtGlobal>
|
||||
#include <QtLogging>
|
||||
|
||||
#include <utility>
|
||||
|
||||
using namespace ToolEnums;
|
||||
|
||||
Chat::Chat(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_id(Network::globalInstance()->generateUniqueId())
|
||||
@@ -38,7 +32,7 @@ Chat::Chat(QObject *parent)
|
||||
connectLLM();
|
||||
}
|
||||
|
||||
Chat::Chat(server_tag_t, QObject *parent)
|
||||
Chat::Chat(bool isServer, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_id(Network::globalInstance()->generateUniqueId())
|
||||
, m_name(tr("Server Chat"))
|
||||
@@ -68,12 +62,13 @@ void Chat::connectLLM()
|
||||
connect(m_llmodel, &ChatLLM::responseStopped, this, &Chat::responseStopped, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::modelLoadingError, this, &Chat::handleModelLoadingError, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::modelLoadingWarning, this, &Chat::modelLoadingWarning, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::restoringFromTextChanged, this, &Chat::handleRestoringFromText, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::generatedNameChanged, this, &Chat::generatedNameChanged, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::generatedQuestionFinished, this, &Chat::generatedQuestionFinished, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::reportSpeed, this, &Chat::handleTokenSpeedChanged, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::loadedModelInfoChanged, this, &Chat::loadedModelInfoChanged, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::databaseResultsChanged, this, &Chat::handleDatabaseResultsChanged, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::modelInfoChanged, this, &Chat::handleModelChanged, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::modelInfoChanged, this, &Chat::handleModelInfoChanged, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::trySwitchContextOfLoadedModelCompleted, this, &Chat::handleTrySwitchContextOfLoadedModelCompleted, Qt::QueuedConnection);
|
||||
|
||||
connect(this, &Chat::promptRequested, m_llmodel, &ChatLLM::prompt, Qt::QueuedConnection);
|
||||
@@ -81,10 +76,11 @@ void Chat::connectLLM()
|
||||
connect(this, &Chat::loadDefaultModelRequested, m_llmodel, &ChatLLM::loadDefaultModel, Qt::QueuedConnection);
|
||||
connect(this, &Chat::generateNameRequested, m_llmodel, &ChatLLM::generateName, Qt::QueuedConnection);
|
||||
connect(this, &Chat::regenerateResponseRequested, m_llmodel, &ChatLLM::regenerateResponse, Qt::QueuedConnection);
|
||||
connect(this, &Chat::resetResponseRequested, m_llmodel, &ChatLLM::resetResponse, Qt::QueuedConnection);
|
||||
connect(this, &Chat::resetContextRequested, m_llmodel, &ChatLLM::resetContext, Qt::QueuedConnection);
|
||||
connect(this, &Chat::processSystemPromptRequested, m_llmodel, &ChatLLM::processSystemPrompt, Qt::QueuedConnection);
|
||||
|
||||
connect(this, &Chat::collectionListChanged, m_collectionModel, &LocalDocsCollectionsModel::setCollections);
|
||||
|
||||
connect(ModelList::globalInstance(), &ModelList::modelInfoChanged, this, &Chat::handleModelInfoChanged);
|
||||
}
|
||||
|
||||
void Chat::reset()
|
||||
@@ -92,15 +88,25 @@ void Chat::reset()
|
||||
stopGenerating();
|
||||
// Erase our current on disk representation as we're completely resetting the chat along with id
|
||||
ChatListModel::globalInstance()->removeChatFile(this);
|
||||
emit resetContextRequested();
|
||||
m_id = Network::globalInstance()->generateUniqueId();
|
||||
emit idChanged(m_id);
|
||||
// NOTE: We deliberately do no reset the name or creation date to indicate that this was originally
|
||||
// an older chat that was reset for another purpose. Resetting this data will lead to the chat
|
||||
// name label changing back to 'New Chat' and showing up in the chat model list as a 'New Chat'
|
||||
// further down in the list. This might surprise the user. In the future, we might get rid of
|
||||
// the "reset context" button in the UI.
|
||||
// the "reset context" button in the UI. Right now, by changing the model in the combobox dropdown
|
||||
// we effectively do a reset context. We *have* to do this right now when switching between different
|
||||
// types of models. The only way to get rid of that would be a very long recalculate where we rebuild
|
||||
// the context if we switch between different types of models. Probably the right way to fix this
|
||||
// is to allow switching models but throwing up a dialog warning users if we switch between types
|
||||
// of models that a long recalculation will ensue.
|
||||
m_chatModel->clear();
|
||||
m_needsSave = true;
|
||||
}
|
||||
|
||||
void Chat::processSystemPrompt()
|
||||
{
|
||||
emit processSystemPromptRequested();
|
||||
}
|
||||
|
||||
void Chat::resetResponseState()
|
||||
@@ -118,88 +124,45 @@ void Chat::resetResponseState()
|
||||
emit responseStateChanged();
|
||||
}
|
||||
|
||||
void Chat::newPromptResponsePair(const QString &prompt, const QList<QUrl> &attachedUrls)
|
||||
{
|
||||
QStringList attachedContexts;
|
||||
QList<PromptAttachment> attachments;
|
||||
for (const QUrl &url : attachedUrls) {
|
||||
Q_ASSERT(url.isLocalFile());
|
||||
const QString localFilePath = url.toLocalFile();
|
||||
const QFileInfo info(localFilePath);
|
||||
|
||||
Q_ASSERT(
|
||||
info.suffix().toLower() == "xlsx" ||
|
||||
info.suffix().toLower() == "txt" ||
|
||||
info.suffix().toLower() == "md" ||
|
||||
info.suffix().toLower() == "rst"
|
||||
);
|
||||
|
||||
PromptAttachment attached;
|
||||
attached.url = url;
|
||||
|
||||
QFile file(localFilePath);
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
attached.content = file.readAll();
|
||||
file.close();
|
||||
} else {
|
||||
qWarning() << "ERROR: Failed to open the attachment:" << localFilePath;
|
||||
continue;
|
||||
}
|
||||
|
||||
attachments << attached;
|
||||
attachedContexts << attached.processedContent();
|
||||
}
|
||||
|
||||
QString promptPlusAttached = prompt;
|
||||
if (!attachedContexts.isEmpty())
|
||||
promptPlusAttached = attachedContexts.join("\n\n") + "\n\n" + prompt;
|
||||
|
||||
resetResponseState();
|
||||
if (int count = m_chatModel->count())
|
||||
m_chatModel->updateCurrentResponse(count - 1, false);
|
||||
m_chatModel->appendPrompt(prompt, attachments);
|
||||
m_chatModel->appendResponse();
|
||||
|
||||
emit promptRequested(m_collections);
|
||||
m_needsSave = true;
|
||||
}
|
||||
|
||||
void Chat::regenerateResponse(int index)
|
||||
void Chat::prompt(const QString &prompt)
|
||||
{
|
||||
resetResponseState();
|
||||
emit regenerateResponseRequested(index);
|
||||
m_needsSave = true;
|
||||
emit promptRequested(m_collections, prompt);
|
||||
}
|
||||
|
||||
QVariant Chat::popPrompt(int index)
|
||||
void Chat::regenerateResponse()
|
||||
{
|
||||
auto content = m_llmodel->popPrompt(index);
|
||||
m_needsSave = true;
|
||||
if (content) return *content;
|
||||
return QVariant::fromValue(nullptr);
|
||||
const int index = m_chatModel->count() - 1;
|
||||
m_chatModel->updateSources(index, QList<ResultInfo>());
|
||||
emit regenerateResponseRequested();
|
||||
}
|
||||
|
||||
void Chat::stopGenerating()
|
||||
{
|
||||
// In future if we have more than one tool we'll have to keep track of which tools are possibly
|
||||
// running, but for now we only have one
|
||||
Tool *toolInstance = ToolModel::globalInstance()->get(ToolCallConstants::CodeInterpreterFunction);
|
||||
Q_ASSERT(toolInstance);
|
||||
toolInstance->interrupt();
|
||||
m_llmodel->stopGenerating();
|
||||
}
|
||||
|
||||
QString Chat::response() const
|
||||
{
|
||||
return m_response;
|
||||
}
|
||||
|
||||
Chat::ResponseState Chat::responseState() const
|
||||
{
|
||||
return m_responseState;
|
||||
}
|
||||
|
||||
void Chat::handleResponseChanged()
|
||||
void Chat::handleResponseChanged(const QString &response)
|
||||
{
|
||||
if (m_responseState != Chat::ResponseGeneration) {
|
||||
m_responseState = Chat::ResponseGeneration;
|
||||
emit responseStateChanged();
|
||||
}
|
||||
|
||||
m_response = response;
|
||||
const int index = m_chatModel->count() - 1;
|
||||
m_chatModel->updateValue(index, this->response());
|
||||
emit responseChanged();
|
||||
}
|
||||
|
||||
void Chat::handleModelLoadingPercentageChanged(float loadingPercentage)
|
||||
@@ -226,7 +189,7 @@ void Chat::handleModelLoadingPercentageChanged(float loadingPercentage)
|
||||
void Chat::promptProcessing()
|
||||
{
|
||||
m_responseState = !databaseResults().isEmpty() ? Chat::LocalDocsProcessing : Chat::PromptProcessing;
|
||||
emit responseStateChanged();
|
||||
emit responseStateChanged();
|
||||
}
|
||||
|
||||
void Chat::generatingQuestions()
|
||||
@@ -239,79 +202,20 @@ void Chat::responseStopped(qint64 promptResponseMs)
|
||||
{
|
||||
m_tokenSpeed = QString();
|
||||
emit tokenSpeedChanged();
|
||||
emit responseChanged();
|
||||
|
||||
m_responseInProgress = false;
|
||||
m_responseState = Chat::ResponseStopped;
|
||||
emit responseInProgressChanged();
|
||||
emit responseStateChanged();
|
||||
if (m_generatedName.isEmpty())
|
||||
emit generateNameRequested();
|
||||
|
||||
const QString possibleToolcall = m_chatModel->possibleToolcall();
|
||||
|
||||
Network::globalInstance()->trackChatEvent("response_stopped", {
|
||||
Network::globalInstance()->trackChatEvent("response_complete", {
|
||||
{"first", m_firstResponse},
|
||||
{"message_count", chatModel()->count()},
|
||||
{"$duration", promptResponseMs / 1000.},
|
||||
});
|
||||
|
||||
ToolCallParser parser;
|
||||
parser.update(possibleToolcall.toUtf8());
|
||||
if (parser.state() == ToolEnums::ParseState::Complete && parser.startTag() != ToolCallConstants::ThinkTag)
|
||||
processToolCall(parser.toolCall());
|
||||
else
|
||||
responseComplete();
|
||||
}
|
||||
|
||||
void Chat::processToolCall(const QString &toolCall)
|
||||
{
|
||||
m_responseState = Chat::ToolCallGeneration;
|
||||
emit responseStateChanged();
|
||||
// Regex to remove the formatting around the code
|
||||
static const QRegularExpression regex("^\\s*```javascript\\s*|\\s*```\\s*$");
|
||||
QString code = toolCall;
|
||||
code.remove(regex);
|
||||
code = code.trimmed();
|
||||
|
||||
// Right now the code interpreter is the only available tool
|
||||
Tool *toolInstance = ToolModel::globalInstance()->get(ToolCallConstants::CodeInterpreterFunction);
|
||||
Q_ASSERT(toolInstance);
|
||||
connect(toolInstance, &Tool::runComplete, this, &Chat::toolCallComplete, Qt::SingleShotConnection);
|
||||
|
||||
// The param is the code
|
||||
const ToolParam param = { "code", ToolEnums::ParamType::String, code };
|
||||
m_responseInProgress = true;
|
||||
emit responseInProgressChanged();
|
||||
toolInstance->run({param});
|
||||
}
|
||||
|
||||
void Chat::toolCallComplete(const ToolCallInfo &info)
|
||||
{
|
||||
// Update the current response with meta information about toolcall and re-parent
|
||||
m_chatModel->updateToolCall(info);
|
||||
|
||||
++m_consecutiveToolCalls;
|
||||
|
||||
m_responseInProgress = false;
|
||||
emit responseInProgressChanged();
|
||||
|
||||
// We limit the number of consecutive toolcalls otherwise we get into a potentially endless loop
|
||||
if (m_consecutiveToolCalls < 3 || info.error == ToolEnums::Error::NoError) {
|
||||
resetResponseState();
|
||||
emit promptRequested(m_collections); // triggers a new response
|
||||
return;
|
||||
}
|
||||
|
||||
responseComplete();
|
||||
}
|
||||
|
||||
void Chat::responseComplete()
|
||||
{
|
||||
if (m_generatedName.isEmpty())
|
||||
emit generateNameRequested();
|
||||
|
||||
m_responseState = Chat::ResponseStopped;
|
||||
emit responseStateChanged();
|
||||
|
||||
m_consecutiveToolCalls = 0;
|
||||
m_firstResponse = false;
|
||||
}
|
||||
|
||||
@@ -322,16 +226,37 @@ ModelInfo Chat::modelInfo() const
|
||||
|
||||
void Chat::setModelInfo(const ModelInfo &modelInfo)
|
||||
{
|
||||
if (m_modelInfo != modelInfo) {
|
||||
m_modelInfo = modelInfo;
|
||||
m_needsSave = true;
|
||||
} else if (isModelLoaded())
|
||||
if (m_modelInfo == modelInfo && isModelLoaded())
|
||||
return;
|
||||
|
||||
m_modelInfo = modelInfo;
|
||||
emit modelInfoChanged();
|
||||
emit modelChangeRequested(modelInfo);
|
||||
}
|
||||
|
||||
void Chat::newPromptResponsePair(const QString &prompt)
|
||||
{
|
||||
resetResponseState();
|
||||
m_chatModel->updateCurrentResponse(m_chatModel->count() - 1, false);
|
||||
m_chatModel->appendPrompt("Prompt: ", prompt);
|
||||
m_chatModel->appendResponse("Response: ", QString());
|
||||
emit resetResponseRequested();
|
||||
}
|
||||
|
||||
// the server needs to block until response is reset, so it calls resetResponse on its own m_llmThread
|
||||
void Chat::serverNewPromptResponsePair(const QString &prompt)
|
||||
{
|
||||
resetResponseState();
|
||||
m_chatModel->updateCurrentResponse(m_chatModel->count() - 1, false);
|
||||
m_chatModel->appendPrompt("Prompt: ", prompt);
|
||||
m_chatModel->appendResponse("Response: ", QString());
|
||||
}
|
||||
|
||||
bool Chat::restoringFromText() const
|
||||
{
|
||||
return m_llmodel->restoringFromText();
|
||||
}
|
||||
|
||||
void Chat::unloadAndDeleteLater()
|
||||
{
|
||||
if (!isModelLoaded()) {
|
||||
@@ -387,14 +312,18 @@ void Chat::generatedNameChanged(const QString &name)
|
||||
int wordCount = qMin(7, words.size());
|
||||
m_name = words.mid(0, wordCount).join(' ');
|
||||
emit nameChanged();
|
||||
m_needsSave = true;
|
||||
}
|
||||
|
||||
void Chat::generatedQuestionFinished(const QString &question)
|
||||
{
|
||||
m_generatedQuestions << question;
|
||||
emit generatedQuestionsChanged();
|
||||
m_needsSave = true;
|
||||
}
|
||||
|
||||
void Chat::handleRestoringFromText()
|
||||
{
|
||||
Network::globalInstance()->trackChatEvent("recalc_context", { {"length", m_chatModel->count()} });
|
||||
emit restoringFromTextChanged();
|
||||
}
|
||||
|
||||
void Chat::handleModelLoadingError(const QString &error)
|
||||
@@ -431,26 +360,17 @@ QString Chat::fallbackReason() const
|
||||
void Chat::handleDatabaseResultsChanged(const QList<ResultInfo> &results)
|
||||
{
|
||||
m_databaseResults = results;
|
||||
m_needsSave = true;
|
||||
const int index = m_chatModel->count() - 1;
|
||||
m_chatModel->updateSources(index, m_databaseResults);
|
||||
}
|
||||
|
||||
// we need to notify listeners of the modelInfo property when its properties are updated,
|
||||
// since it's a gadget and can't do that on its own
|
||||
void Chat::handleModelInfoChanged(const ModelInfo &modelInfo)
|
||||
{
|
||||
if (!m_modelInfo.id().isNull() && modelInfo.id() == m_modelInfo.id())
|
||||
emit modelInfoChanged();
|
||||
}
|
||||
|
||||
// react if a new model is loaded
|
||||
void Chat::handleModelChanged(const ModelInfo &modelInfo)
|
||||
{
|
||||
if (m_modelInfo == modelInfo)
|
||||
return;
|
||||
|
||||
m_modelInfo = modelInfo;
|
||||
emit modelInfoChanged();
|
||||
m_needsSave = true;
|
||||
}
|
||||
|
||||
void Chat::handleTrySwitchContextOfLoadedModelCompleted(int value)
|
||||
@@ -465,14 +385,17 @@ bool Chat::serialize(QDataStream &stream, int version) const
|
||||
stream << m_id;
|
||||
stream << m_name;
|
||||
stream << m_userName;
|
||||
if (version >= 5)
|
||||
if (version > 4)
|
||||
stream << m_modelInfo.id();
|
||||
else
|
||||
stream << m_modelInfo.filename();
|
||||
if (version >= 3)
|
||||
if (version > 2)
|
||||
stream << m_collections;
|
||||
|
||||
if (!m_llmodel->serialize(stream, version))
|
||||
const bool serializeKV = MySettings::globalInstance()->saveChatsContext();
|
||||
if (version > 5)
|
||||
stream << serializeKV;
|
||||
if (!m_llmodel->serialize(stream, version, serializeKV))
|
||||
return false;
|
||||
if (!m_chatModel->serialize(stream, version))
|
||||
return false;
|
||||
@@ -491,7 +414,7 @@ bool Chat::deserialize(QDataStream &stream, int version)
|
||||
|
||||
QString modelId;
|
||||
stream >> modelId;
|
||||
if (version >= 5) {
|
||||
if (version > 4) {
|
||||
if (ModelList::globalInstance()->contains(modelId))
|
||||
m_modelInfo = ModelList::globalInstance()->modelInfo(modelId);
|
||||
} else {
|
||||
@@ -501,23 +424,27 @@ bool Chat::deserialize(QDataStream &stream, int version)
|
||||
if (!m_modelInfo.id().isEmpty())
|
||||
emit modelInfoChanged();
|
||||
|
||||
if (version >= 3) {
|
||||
bool discardKV = m_modelInfo.id().isEmpty();
|
||||
|
||||
if (version > 2) {
|
||||
stream >> m_collections;
|
||||
emit collectionListChanged(m_collections);
|
||||
}
|
||||
|
||||
bool deserializeKV = true;
|
||||
if (version > 5)
|
||||
stream >> deserializeKV;
|
||||
|
||||
m_llmodel->setModelInfo(m_modelInfo);
|
||||
if (!m_llmodel->deserialize(stream, version))
|
||||
if (!m_llmodel->deserialize(stream, version, deserializeKV, discardKV))
|
||||
return false;
|
||||
if (!m_chatModel->deserialize(stream, version))
|
||||
return false;
|
||||
|
||||
emit chatModelChanged();
|
||||
if (stream.status() != QDataStream::Ok)
|
||||
return false;
|
||||
m_llmodel->setStateFromText(m_chatModel->text());
|
||||
|
||||
m_needsSave = false;
|
||||
return true;
|
||||
emit chatModelChanged();
|
||||
return stream.status() == QDataStream::Ok;
|
||||
}
|
||||
|
||||
QList<QString> Chat::collectionList() const
|
||||
@@ -537,7 +464,6 @@ void Chat::addCollection(const QString &collection)
|
||||
|
||||
m_collections.append(collection);
|
||||
emit collectionListChanged(m_collections);
|
||||
m_needsSave = true;
|
||||
}
|
||||
|
||||
void Chat::removeCollection(const QString &collection)
|
||||
@@ -547,5 +473,4 @@ void Chat::removeCollection(const QString &collection)
|
||||
|
||||
m_collections.removeAll(collection);
|
||||
emit collectionListChanged(m_collections);
|
||||
m_needsSave = true;
|
||||
}
|
||||
|
||||
@@ -7,13 +7,10 @@
|
||||
#include "localdocsmodel.h" // IWYU pragma: keep
|
||||
#include "modellist.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
#include <QString>
|
||||
#include <QStringList> // IWYU pragma: keep
|
||||
#include <QStringView>
|
||||
#include <QtGlobal>
|
||||
|
||||
class QDataStream;
|
||||
@@ -27,13 +24,15 @@ class Chat : public QObject
|
||||
Q_PROPERTY(bool isModelLoaded READ isModelLoaded NOTIFY isModelLoadedChanged)
|
||||
Q_PROPERTY(bool isCurrentlyLoading READ isCurrentlyLoading NOTIFY isCurrentlyLoadingChanged)
|
||||
Q_PROPERTY(float modelLoadingPercentage READ modelLoadingPercentage NOTIFY modelLoadingPercentageChanged)
|
||||
Q_PROPERTY(QString response READ response NOTIFY responseChanged)
|
||||
Q_PROPERTY(ModelInfo modelInfo READ modelInfo WRITE setModelInfo NOTIFY modelInfoChanged)
|
||||
Q_PROPERTY(bool responseInProgress READ responseInProgress NOTIFY responseInProgressChanged)
|
||||
Q_PROPERTY(bool restoringFromText READ restoringFromText NOTIFY restoringFromTextChanged)
|
||||
Q_PROPERTY(bool isServer READ isServer NOTIFY isServerChanged)
|
||||
Q_PROPERTY(ResponseState responseState READ responseState NOTIFY responseStateChanged)
|
||||
Q_PROPERTY(QList<QString> collectionList READ collectionList NOTIFY collectionListChanged)
|
||||
Q_PROPERTY(QString modelLoadingError READ modelLoadingError NOTIFY modelLoadingErrorChanged)
|
||||
Q_PROPERTY(QString tokenSpeed READ tokenSpeed NOTIFY tokenSpeedChanged)
|
||||
Q_PROPERTY(QString tokenSpeed READ tokenSpeed NOTIFY tokenSpeedChanged);
|
||||
Q_PROPERTY(QString deviceBackend READ deviceBackend NOTIFY loadedModelInfoChanged)
|
||||
Q_PROPERTY(QString device READ device NOTIFY loadedModelInfoChanged)
|
||||
Q_PROPERTY(QString fallbackReason READ fallbackReason NOTIFY loadedModelInfoChanged)
|
||||
@@ -45,23 +44,18 @@ class Chat : public QObject
|
||||
QML_UNCREATABLE("Only creatable from c++!")
|
||||
|
||||
public:
|
||||
// tag for constructing a server chat
|
||||
struct server_tag_t { explicit server_tag_t() = default; };
|
||||
static inline constexpr server_tag_t server_tag = server_tag_t();
|
||||
|
||||
enum ResponseState {
|
||||
ResponseStopped,
|
||||
LocalDocsRetrieval,
|
||||
LocalDocsProcessing,
|
||||
PromptProcessing,
|
||||
GeneratingQuestions,
|
||||
ResponseGeneration,
|
||||
ToolCallGeneration
|
||||
ResponseGeneration
|
||||
};
|
||||
Q_ENUM(ResponseState)
|
||||
|
||||
explicit Chat(QObject *parent = nullptr);
|
||||
explicit Chat(server_tag_t, QObject *parent = nullptr);
|
||||
explicit Chat(bool isServer, QObject *parent = nullptr);
|
||||
virtual ~Chat();
|
||||
void destroy() { m_llmodel->destroy(); }
|
||||
void connectLLM();
|
||||
@@ -72,27 +66,29 @@ public:
|
||||
{
|
||||
m_userName = name;
|
||||
emit nameChanged();
|
||||
m_needsSave = true;
|
||||
}
|
||||
ChatModel *chatModel() { return m_chatModel; }
|
||||
|
||||
bool isNewChat() const { return m_name == tr("New Chat") && !m_chatModel->count(); }
|
||||
|
||||
Q_INVOKABLE void reset();
|
||||
Q_INVOKABLE void processSystemPrompt();
|
||||
bool isModelLoaded() const { return m_modelLoadingPercentage == 1.0f; }
|
||||
bool isCurrentlyLoading() const { return m_modelLoadingPercentage > 0.0f && m_modelLoadingPercentage < 1.0f; }
|
||||
float modelLoadingPercentage() const { return m_modelLoadingPercentage; }
|
||||
Q_INVOKABLE void newPromptResponsePair(const QString &prompt, const QList<QUrl> &attachedUrls = {});
|
||||
Q_INVOKABLE void regenerateResponse(int index);
|
||||
Q_INVOKABLE QVariant popPrompt(int index);
|
||||
Q_INVOKABLE void prompt(const QString &prompt);
|
||||
Q_INVOKABLE void regenerateResponse();
|
||||
Q_INVOKABLE void stopGenerating();
|
||||
Q_INVOKABLE void newPromptResponsePair(const QString &prompt);
|
||||
|
||||
QList<ResultInfo> databaseResults() const { return m_databaseResults; }
|
||||
|
||||
QString response() const;
|
||||
bool responseInProgress() const { return m_responseInProgress; }
|
||||
ResponseState responseState() const;
|
||||
ModelInfo modelInfo() const;
|
||||
void setModelInfo(const ModelInfo &modelInfo);
|
||||
bool restoringFromText() const;
|
||||
|
||||
Q_INVOKABLE void unloadModel();
|
||||
Q_INVOKABLE void reloadModel();
|
||||
@@ -113,6 +109,7 @@ public:
|
||||
Q_INVOKABLE bool hasCollection(const QString &collection) const;
|
||||
Q_INVOKABLE void addCollection(const QString &collection);
|
||||
Q_INVOKABLE void removeCollection(const QString &collection);
|
||||
void resetResponseState();
|
||||
|
||||
QString modelLoadingError() const { return m_modelLoadingError; }
|
||||
|
||||
@@ -126,11 +123,8 @@ public:
|
||||
|
||||
QList<QString> generatedQuestions() const { return m_generatedQuestions; }
|
||||
|
||||
bool needsSave() const { return m_needsSave; }
|
||||
void setNeedsSave(bool n) { m_needsSave = n; }
|
||||
|
||||
public Q_SLOTS:
|
||||
void resetResponseState();
|
||||
void serverNewPromptResponsePair(const QString &prompt);
|
||||
|
||||
Q_SIGNALS:
|
||||
void idChanged(const QString &id);
|
||||
@@ -140,14 +134,17 @@ Q_SIGNALS:
|
||||
void isCurrentlyLoadingChanged();
|
||||
void modelLoadingPercentageChanged();
|
||||
void modelLoadingWarning(const QString &warning);
|
||||
void responseChanged();
|
||||
void responseInProgressChanged();
|
||||
void responseStateChanged();
|
||||
void promptRequested(const QStringList &enabledCollections);
|
||||
void regenerateResponseRequested(int index);
|
||||
void promptRequested(const QList<QString> &collectionList, const QString &prompt);
|
||||
void regenerateResponseRequested();
|
||||
void resetResponseRequested();
|
||||
void resetContextRequested();
|
||||
void processSystemPromptRequested();
|
||||
void modelChangeRequested(const ModelInfo &modelInfo);
|
||||
void modelInfoChanged();
|
||||
void restoringFromTextChanged();
|
||||
void loadDefaultModelRequested();
|
||||
void generateNameRequested();
|
||||
void modelLoadingErrorChanged();
|
||||
@@ -162,21 +159,18 @@ Q_SIGNALS:
|
||||
void generatedQuestionsChanged();
|
||||
|
||||
private Q_SLOTS:
|
||||
void handleResponseChanged();
|
||||
void handleResponseChanged(const QString &response);
|
||||
void handleModelLoadingPercentageChanged(float);
|
||||
void promptProcessing();
|
||||
void generatingQuestions();
|
||||
void responseStopped(qint64 promptResponseMs);
|
||||
void processToolCall(const QString &toolCall);
|
||||
void toolCallComplete(const ToolCallInfo &info);
|
||||
void responseComplete();
|
||||
void generatedNameChanged(const QString &name);
|
||||
void generatedQuestionFinished(const QString &question);
|
||||
void handleRestoringFromText();
|
||||
void handleModelLoadingError(const QString &error);
|
||||
void handleTokenSpeedChanged(const QString &tokenSpeed);
|
||||
void handleDatabaseResultsChanged(const QList<ResultInfo> &results);
|
||||
void handleModelInfoChanged(const ModelInfo &modelInfo);
|
||||
void handleModelChanged(const ModelInfo &modelInfo);
|
||||
void handleTrySwitchContextOfLoadedModelCompleted(int value);
|
||||
|
||||
private:
|
||||
@@ -189,6 +183,7 @@ private:
|
||||
QString m_tokenSpeed;
|
||||
QString m_device;
|
||||
QString m_fallbackReason;
|
||||
QString m_response;
|
||||
QList<QString> m_collections;
|
||||
QList<QString> m_generatedQuestions;
|
||||
ChatModel *m_chatModel;
|
||||
@@ -204,11 +199,6 @@ private:
|
||||
bool m_firstResponse = true;
|
||||
int m_trySwitchContextInProgress = 0;
|
||||
bool m_isCurrentlyLoading = false;
|
||||
// True if we need to serialize the chat to disk, because of one of two reasons:
|
||||
// - The chat was freshly created during this launch.
|
||||
// - The chat was changed after loading it from disk.
|
||||
bool m_needsSave = true;
|
||||
int m_consecutiveToolCalls = 0;
|
||||
};
|
||||
|
||||
#endif // CHAT_H
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include "chatapi.h"
|
||||
|
||||
#include "utils.h"
|
||||
#include <gpt4all-backend/llmodel.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QGuiApplication>
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
@@ -13,17 +13,12 @@
|
||||
#include <QNetworkRequest>
|
||||
#include <QThread>
|
||||
#include <QUrl>
|
||||
#include <QUtf8StringView>
|
||||
#include <QVariant>
|
||||
#include <QXmlStreamReader>
|
||||
#include <Qt>
|
||||
#include <QtGlobal>
|
||||
#include <QtLogging>
|
||||
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
@@ -56,6 +51,7 @@ bool ChatAPI::loadModel(const std::string &modelPath, int n_ctx, int ngl)
|
||||
void ChatAPI::setThreadCount(int32_t n_threads)
|
||||
{
|
||||
Q_UNUSED(n_threads);
|
||||
qt_noop();
|
||||
}
|
||||
|
||||
int32_t ChatAPI::threadCount() const
|
||||
@@ -72,119 +68,89 @@ bool ChatAPI::isModelLoaded() const
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto parsePrompt(QXmlStreamReader &xml) -> std::expected<QJsonArray, QString>
|
||||
// All three of the state virtual functions are handled custom inside of chatllm save/restore
|
||||
size_t ChatAPI::stateSize() const
|
||||
{
|
||||
QJsonArray messages;
|
||||
|
||||
auto xmlError = [&xml] {
|
||||
return std::unexpected(u"%1:%2: %3"_s.arg(xml.lineNumber()).arg(xml.columnNumber()).arg(xml.errorString()));
|
||||
};
|
||||
|
||||
if (xml.hasError())
|
||||
return xmlError();
|
||||
if (xml.atEnd())
|
||||
return messages;
|
||||
|
||||
// skip header
|
||||
bool foundElement = false;
|
||||
do {
|
||||
switch (xml.readNext()) {
|
||||
using enum QXmlStreamReader::TokenType;
|
||||
case Invalid:
|
||||
return xmlError();
|
||||
case EndDocument:
|
||||
return messages;
|
||||
default:
|
||||
foundElement = true;
|
||||
case StartDocument:
|
||||
case Comment:
|
||||
case DTD:
|
||||
case ProcessingInstruction:
|
||||
;
|
||||
}
|
||||
} while (!foundElement);
|
||||
|
||||
// document body loop
|
||||
bool foundRoot = false;
|
||||
for (;;) {
|
||||
switch (xml.tokenType()) {
|
||||
using enum QXmlStreamReader::TokenType;
|
||||
case StartElement:
|
||||
{
|
||||
auto name = xml.name();
|
||||
if (!foundRoot) {
|
||||
if (name != "chat"_L1)
|
||||
return std::unexpected(u"unexpected tag: %1"_s.arg(name));
|
||||
foundRoot = true;
|
||||
} else {
|
||||
if (name != "user"_L1 && name != "assistant"_L1 && name != "system"_L1)
|
||||
return std::unexpected(u"unknown role: %1"_s.arg(name));
|
||||
auto content = xml.readElementText();
|
||||
if (xml.tokenType() != EndElement)
|
||||
return xmlError();
|
||||
messages << makeJsonObject({
|
||||
{ "role"_L1, name.toString().trimmed() },
|
||||
{ "content"_L1, content },
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Characters:
|
||||
if (!xml.isWhitespace())
|
||||
return std::unexpected(u"unexpected text: %1"_s.arg(xml.text()));
|
||||
case Comment:
|
||||
case ProcessingInstruction:
|
||||
case EndElement:
|
||||
break;
|
||||
case EndDocument:
|
||||
return messages;
|
||||
case Invalid:
|
||||
return xmlError();
|
||||
default:
|
||||
return std::unexpected(u"unexpected token: %1"_s.arg(xml.tokenString()));
|
||||
}
|
||||
xml.readNext();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ChatAPI::prompt(
|
||||
std::string_view prompt,
|
||||
const PromptCallback &promptCallback,
|
||||
const ResponseCallback &responseCallback,
|
||||
const PromptContext &promptCtx
|
||||
) {
|
||||
Q_UNUSED(promptCallback)
|
||||
size_t ChatAPI::saveState(uint8_t *dest) const
|
||||
{
|
||||
Q_UNUSED(dest);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!isModelLoaded())
|
||||
throw std::invalid_argument("Attempted to prompt an unloaded model.");
|
||||
if (!promptCtx.n_predict)
|
||||
return; // nothing requested
|
||||
size_t ChatAPI::restoreState(const uint8_t *src)
|
||||
{
|
||||
Q_UNUSED(src);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ChatAPI::prompt(const std::string &prompt,
|
||||
const std::string &promptTemplate,
|
||||
std::function<bool(int32_t)> promptCallback,
|
||||
std::function<bool(int32_t, const std::string&)> responseCallback,
|
||||
bool allowContextShift,
|
||||
PromptContext &promptCtx,
|
||||
bool special,
|
||||
std::optional<std::string_view> fakeReply) {
|
||||
|
||||
Q_UNUSED(promptCallback);
|
||||
Q_UNUSED(allowContextShift);
|
||||
Q_UNUSED(special);
|
||||
|
||||
if (!isModelLoaded()) {
|
||||
std::cerr << "ChatAPI ERROR: prompt won't work with an unloaded model!\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!promptCtx.n_past) { m_queuedPrompts.clear(); }
|
||||
Q_ASSERT(promptCtx.n_past <= m_context.size());
|
||||
m_context.resize(promptCtx.n_past);
|
||||
|
||||
// FIXME(cebtenzzre): We're assuming people don't try to use %2 with ChatGPT. What would that even mean?
|
||||
m_queuedPrompts << QString::fromStdString(promptTemplate).arg(QString::fromStdString(prompt));
|
||||
|
||||
if (!promptCtx.n_predict && !fakeReply) {
|
||||
return; // response explicitly suppressed, queue prompt for later
|
||||
}
|
||||
|
||||
QString formattedPrompt = m_queuedPrompts.join("");
|
||||
m_queuedPrompts.clear();
|
||||
|
||||
if (fakeReply) {
|
||||
promptCtx.n_past += 1;
|
||||
m_context.append(formattedPrompt);
|
||||
m_context.append(QString::fromUtf8(fakeReply->data(), fakeReply->size()));
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: We don't set the max_tokens on purpose because in order to do so safely without encountering
|
||||
// an error we need to be able to count the tokens in our prompt. The only way to do this is to use
|
||||
// the OpenAI tiktoken library or to implement our own tokenization function that matches precisely
|
||||
// the OpenAI tiktokken library or to implement our own tokenization function that matches precisely
|
||||
// the tokenization used by the OpenAI model we're calling. OpenAI has not introduced any means of
|
||||
// using the REST API to count tokens in a prompt.
|
||||
auto root = makeJsonObject({
|
||||
{ "model"_L1, m_modelName },
|
||||
{ "stream"_L1, true },
|
||||
{ "temperature"_L1, promptCtx.temp },
|
||||
{ "top_p"_L1, promptCtx.top_p },
|
||||
});
|
||||
QJsonObject root;
|
||||
root.insert("model", m_modelName);
|
||||
root.insert("stream", true);
|
||||
root.insert("temperature", promptCtx.temp);
|
||||
root.insert("top_p", promptCtx.top_p);
|
||||
|
||||
// conversation history
|
||||
{
|
||||
QUtf8StringView promptUtf8(prompt);
|
||||
QXmlStreamReader xml(promptUtf8);
|
||||
auto messages = parsePrompt(xml);
|
||||
if (!messages) {
|
||||
auto error = fmt::format("Failed to parse API model prompt: {}", messages.error());
|
||||
qDebug().noquote() << "ChatAPI ERROR:" << error << "Prompt:\n\n" << promptUtf8 << '\n';
|
||||
throw std::invalid_argument(error);
|
||||
}
|
||||
root.insert("messages"_L1, *messages);
|
||||
QJsonArray messages;
|
||||
for (int i = 0; i < m_context.count(); ++i) {
|
||||
QJsonObject message;
|
||||
message.insert("role", i % 2 == 0 ? "user" : "assistant");
|
||||
message.insert("content", m_context.at(i));
|
||||
messages.append(message);
|
||||
}
|
||||
|
||||
QJsonObject promptObject;
|
||||
promptObject.insert("role", "user");
|
||||
promptObject.insert("content", formattedPrompt);
|
||||
messages.append(promptObject);
|
||||
root.insert("messages", messages);
|
||||
|
||||
QJsonDocument doc(root);
|
||||
|
||||
#if defined(DEBUG)
|
||||
@@ -201,9 +167,12 @@ void ChatAPI::prompt(
|
||||
connect(&worker, &ChatAPIWorker::finished, &workerThread, &QThread::quit, Qt::DirectConnection);
|
||||
connect(this, &ChatAPI::request, &worker, &ChatAPIWorker::request, Qt::QueuedConnection);
|
||||
workerThread.start();
|
||||
emit request(m_apiKey, doc.toJson(QJsonDocument::Compact));
|
||||
emit request(m_apiKey, &promptCtx, doc.toJson(QJsonDocument::Compact));
|
||||
workerThread.wait();
|
||||
|
||||
promptCtx.n_past += 1;
|
||||
m_context.append(formattedPrompt);
|
||||
m_context.append(worker.currentResponse());
|
||||
m_responseCallback = nullptr;
|
||||
|
||||
#if defined(DEBUG)
|
||||
@@ -221,8 +190,12 @@ bool ChatAPI::callResponse(int32_t token, const std::string& string)
|
||||
return m_responseCallback(token, string);
|
||||
}
|
||||
|
||||
void ChatAPIWorker::request(const QString &apiKey, const QByteArray &array)
|
||||
void ChatAPIWorker::request(const QString &apiKey,
|
||||
LLModel::PromptContext *promptCtx,
|
||||
const QByteArray &array)
|
||||
{
|
||||
m_ctx = promptCtx;
|
||||
|
||||
QUrl apiUrl(m_chat->url());
|
||||
const QString authorization = u"Bearer %1"_s.arg(apiKey).trimmed();
|
||||
QNetworkRequest request(apiUrl);
|
||||
@@ -329,6 +302,7 @@ void ChatAPIWorker::handleReadyRead()
|
||||
const QJsonObject choice = choices.first().toObject();
|
||||
const QJsonObject delta = choice.value("delta").toObject();
|
||||
const QString content = delta.value("content").toString();
|
||||
Q_ASSERT(m_ctx);
|
||||
m_currentResponse += content;
|
||||
if (!m_chat->callResponse(0, content.toStdString())) {
|
||||
reply->abort();
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
|
||||
#include <gpt4all-backend/llmodel.h>
|
||||
|
||||
#include <QByteArray> // IWYU pragma: keep
|
||||
#include <QByteArray>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QList>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
@@ -25,13 +26,16 @@ class ChatAPIWorker : public QObject {
|
||||
public:
|
||||
ChatAPIWorker(ChatAPI *chatAPI)
|
||||
: QObject(nullptr)
|
||||
, m_ctx(nullptr)
|
||||
, m_networkManager(nullptr)
|
||||
, m_chat(chatAPI) {}
|
||||
virtual ~ChatAPIWorker() {}
|
||||
|
||||
QString currentResponse() const { return m_currentResponse; }
|
||||
|
||||
void request(const QString &apiKey, const QByteArray &array);
|
||||
void request(const QString &apiKey,
|
||||
LLModel::PromptContext *promptCtx,
|
||||
const QByteArray &array);
|
||||
|
||||
Q_SIGNALS:
|
||||
void finished();
|
||||
@@ -43,6 +47,7 @@ private Q_SLOTS:
|
||||
|
||||
private:
|
||||
ChatAPI *m_chat;
|
||||
LLModel::PromptContext *m_ctx;
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QString m_currentResponse;
|
||||
};
|
||||
@@ -58,23 +63,17 @@ public:
|
||||
bool loadModel(const std::string &modelPath, int n_ctx, int ngl) override;
|
||||
bool isModelLoaded() const override;
|
||||
size_t requiredMem(const std::string &modelPath, int n_ctx, int ngl) override;
|
||||
|
||||
// All three of the state virtual functions are handled custom inside of chatllm save/restore
|
||||
size_t stateSize() const override
|
||||
{ throwNotImplemented(); }
|
||||
size_t saveState(std::span<uint8_t> stateOut, std::vector<Token> &inputTokensOut) const override
|
||||
{ Q_UNUSED(stateOut); Q_UNUSED(inputTokensOut); throwNotImplemented(); }
|
||||
size_t restoreState(std::span<const uint8_t> state, std::span<const Token> inputTokens) override
|
||||
{ Q_UNUSED(state); Q_UNUSED(inputTokens); throwNotImplemented(); }
|
||||
|
||||
void prompt(std::string_view prompt,
|
||||
const PromptCallback &promptCallback,
|
||||
const ResponseCallback &responseCallback,
|
||||
const PromptContext &ctx) override;
|
||||
|
||||
[[noreturn]]
|
||||
int32_t countPromptTokens(std::string_view prompt) const override
|
||||
{ Q_UNUSED(prompt); throwNotImplemented(); }
|
||||
size_t stateSize() const override;
|
||||
size_t saveState(uint8_t *dest) const override;
|
||||
size_t restoreState(const uint8_t *src) override;
|
||||
void prompt(const std::string &prompt,
|
||||
const std::string &promptTemplate,
|
||||
std::function<bool(int32_t)> promptCallback,
|
||||
std::function<bool(int32_t, const std::string&)> responseCallback,
|
||||
bool allowContextShift,
|
||||
PromptContext &ctx,
|
||||
bool special,
|
||||
std::optional<std::string_view> fakeReply) override;
|
||||
|
||||
void setThreadCount(int32_t n_threads) override;
|
||||
int32_t threadCount() const override;
|
||||
@@ -84,87 +83,82 @@ public:
|
||||
void setRequestURL(const QString &requestURL) { m_requestURL = requestURL; }
|
||||
QString url() const { return m_requestURL; }
|
||||
|
||||
QList<QString> context() const { return m_context; }
|
||||
void setContext(const QList<QString> &context) { m_context = context; }
|
||||
|
||||
bool callResponse(int32_t token, const std::string &string);
|
||||
|
||||
[[noreturn]]
|
||||
int32_t contextLength() const override
|
||||
{ throwNotImplemented(); }
|
||||
|
||||
auto specialTokens() -> std::unordered_map<std::string, std::string> const override
|
||||
{ return {}; }
|
||||
|
||||
Q_SIGNALS:
|
||||
void request(const QString &apiKey, const QByteArray &array);
|
||||
void request(const QString &apiKey,
|
||||
LLModel::PromptContext *ctx,
|
||||
const QByteArray &array);
|
||||
|
||||
protected:
|
||||
// We have to implement these as they are pure virtual in base class, but we don't actually use
|
||||
// them as they are only called from the default implementation of 'prompt' which we override and
|
||||
// completely replace
|
||||
|
||||
[[noreturn]]
|
||||
static void throwNotImplemented() { throw std::logic_error("not implemented"); }
|
||||
std::vector<Token> tokenize(PromptContext &ctx, std::string_view str, bool special) override
|
||||
{
|
||||
(void)ctx;
|
||||
(void)str;
|
||||
(void)special;
|
||||
throw std::logic_error("not implemented");
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
std::vector<Token> tokenize(std::string_view str) const override
|
||||
{ Q_UNUSED(str); throwNotImplemented(); }
|
||||
|
||||
[[noreturn]]
|
||||
bool isSpecialToken(Token id) const override
|
||||
{ Q_UNUSED(id); throwNotImplemented(); }
|
||||
{
|
||||
(void)id;
|
||||
throw std::logic_error("not implemented");
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
std::string tokenToString(Token id) const override
|
||||
{ Q_UNUSED(id); throwNotImplemented(); }
|
||||
{
|
||||
(void)id;
|
||||
throw std::logic_error("not implemented");
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
void initSampler(const PromptContext &ctx) override
|
||||
{ Q_UNUSED(ctx); throwNotImplemented(); }
|
||||
Token sampleToken(PromptContext &ctx) const override
|
||||
{
|
||||
(void)ctx;
|
||||
throw std::logic_error("not implemented");
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
Token sampleToken() const override
|
||||
{ throwNotImplemented(); }
|
||||
bool evalTokens(PromptContext &ctx, const std::vector<int32_t> &tokens) const override
|
||||
{
|
||||
(void)ctx;
|
||||
(void)tokens;
|
||||
throw std::logic_error("not implemented");
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
bool evalTokens(int32_t nPast, std::span<const Token> tokens) const override
|
||||
{ Q_UNUSED(nPast); Q_UNUSED(tokens); throwNotImplemented(); }
|
||||
void shiftContext(PromptContext &promptCtx) override
|
||||
{
|
||||
(void)promptCtx;
|
||||
throw std::logic_error("not implemented");
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
void shiftContext(const PromptContext &promptCtx, int32_t *nPast) override
|
||||
{ Q_UNUSED(promptCtx); Q_UNUSED(nPast); throwNotImplemented(); }
|
||||
int32_t contextLength() const override
|
||||
{
|
||||
throw std::logic_error("not implemented");
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
int32_t inputLength() const override
|
||||
{ throwNotImplemented(); }
|
||||
|
||||
[[noreturn]]
|
||||
int32_t computeModelInputPosition(std::span<const Token> input) const override
|
||||
{ Q_UNUSED(input); throwNotImplemented(); }
|
||||
|
||||
[[noreturn]]
|
||||
void setModelInputPosition(int32_t pos) override
|
||||
{ Q_UNUSED(pos); throwNotImplemented(); }
|
||||
|
||||
[[noreturn]]
|
||||
void appendInputToken(Token tok) override
|
||||
{ Q_UNUSED(tok); throwNotImplemented(); }
|
||||
|
||||
[[noreturn]]
|
||||
const std::vector<Token> &endTokens() const override
|
||||
{ throwNotImplemented(); }
|
||||
{
|
||||
throw std::logic_error("not implemented");
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
bool shouldAddBOS() const override
|
||||
{ throwNotImplemented(); }
|
||||
|
||||
[[noreturn]]
|
||||
std::span<const Token> inputTokens() const override
|
||||
{ throwNotImplemented(); }
|
||||
{
|
||||
throw std::logic_error("not implemented");
|
||||
}
|
||||
|
||||
private:
|
||||
ResponseCallback m_responseCallback;
|
||||
QString m_modelName;
|
||||
QString m_apiKey;
|
||||
QString m_requestURL;
|
||||
std::function<bool(int32_t, const std::string&)> m_responseCallback;
|
||||
QString m_modelName;
|
||||
QString m_apiKey;
|
||||
QString m_requestURL;
|
||||
QList<QString> m_context;
|
||||
QStringList m_queuedPrompts;
|
||||
};
|
||||
|
||||
#endif // CHATAPI_H
|
||||
|
||||
@@ -17,10 +17,9 @@
|
||||
#include <Qt>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
static constexpr quint32 CHAT_FORMAT_MAGIC = 0xF5D553CC;
|
||||
static constexpr qint32 CHAT_FORMAT_VERSION = 12;
|
||||
#define CHAT_FORMAT_MAGIC 0xF5D553CC
|
||||
#define CHAT_FORMAT_VERSION 9
|
||||
|
||||
class MyChatListModel: public ChatListModel { };
|
||||
Q_GLOBAL_STATIC(MyChatListModel, chatListModelInstance)
|
||||
@@ -52,12 +51,6 @@ void ChatListModel::loadChats()
|
||||
connect(thread, &ChatsRestoreThread::finished, thread, &QObject::deleteLater);
|
||||
thread->start();
|
||||
|
||||
m_chatSaver = std::make_unique<ChatSaver>();
|
||||
connect(this, &ChatListModel::requestSaveChats, m_chatSaver.get(), &ChatSaver::saveChats, Qt::QueuedConnection);
|
||||
connect(m_chatSaver.get(), &ChatSaver::saveChatsFinished, this, &ChatListModel::saveChatsFinished, Qt::QueuedConnection);
|
||||
// save chats on application quit
|
||||
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &ChatListModel::saveChatsSync);
|
||||
|
||||
connect(MySettings::globalInstance(), &MySettings::serverChatChanged, this, &ChatListModel::handleServerEnabledChanged);
|
||||
}
|
||||
|
||||
@@ -80,59 +73,33 @@ ChatSaver::ChatSaver()
|
||||
m_thread.start();
|
||||
}
|
||||
|
||||
ChatSaver::~ChatSaver()
|
||||
{
|
||||
m_thread.quit();
|
||||
m_thread.wait();
|
||||
}
|
||||
|
||||
QVector<Chat *> ChatListModel::getChatsToSave() const
|
||||
{
|
||||
QVector<Chat *> toSave;
|
||||
for (auto *chat : m_chats)
|
||||
if (chat != m_serverChat && !chat->isNewChat())
|
||||
toSave << chat;
|
||||
return toSave;
|
||||
}
|
||||
|
||||
void ChatListModel::saveChats()
|
||||
{
|
||||
auto toSave = getChatsToSave();
|
||||
QVector<Chat*> toSave;
|
||||
for (Chat *chat : m_chats) {
|
||||
if (chat == m_serverChat)
|
||||
continue;
|
||||
if (chat->isNewChat())
|
||||
continue;
|
||||
toSave.append(chat);
|
||||
}
|
||||
if (toSave.isEmpty()) {
|
||||
emit saveChatsFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
ChatSaver *saver = new ChatSaver;
|
||||
connect(this, &ChatListModel::requestSaveChats, saver, &ChatSaver::saveChats, Qt::QueuedConnection);
|
||||
connect(saver, &ChatSaver::saveChatsFinished, this, &ChatListModel::saveChatsFinished, Qt::QueuedConnection);
|
||||
emit requestSaveChats(toSave);
|
||||
}
|
||||
|
||||
void ChatListModel::saveChatsForQuit()
|
||||
{
|
||||
saveChats();
|
||||
m_startedFinalSave = true;
|
||||
}
|
||||
|
||||
void ChatListModel::saveChatsSync()
|
||||
{
|
||||
auto toSave = getChatsToSave();
|
||||
if (!m_startedFinalSave && !toSave.isEmpty())
|
||||
m_chatSaver->saveChats(toSave);
|
||||
}
|
||||
|
||||
void ChatSaver::saveChats(const QVector<Chat *> &chats)
|
||||
{
|
||||
// we can be called from the main thread instead of a worker thread at quit time, so take a lock
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
const QString savePath = MySettings::globalInstance()->modelPath();
|
||||
qsizetype nSavedChats = 0;
|
||||
for (Chat *chat : chats) {
|
||||
if (!chat->needsSave())
|
||||
continue;
|
||||
++nSavedChats;
|
||||
|
||||
QString fileName = "gpt4all-" + chat->id() + ".chat";
|
||||
QString filePath = savePath + "/" + fileName;
|
||||
QFile originalFile(filePath);
|
||||
@@ -145,8 +112,8 @@ void ChatSaver::saveChats(const QVector<Chat *> &chats)
|
||||
}
|
||||
QDataStream out(&tempFile);
|
||||
|
||||
out << CHAT_FORMAT_MAGIC;
|
||||
out << CHAT_FORMAT_VERSION;
|
||||
out << (quint32)CHAT_FORMAT_MAGIC;
|
||||
out << (qint32)CHAT_FORMAT_VERSION;
|
||||
out.setVersion(QDataStream::Qt_6_2);
|
||||
|
||||
qDebug() << "serializing chat" << fileName;
|
||||
@@ -156,14 +123,13 @@ void ChatSaver::saveChats(const QVector<Chat *> &chats)
|
||||
continue;
|
||||
}
|
||||
|
||||
chat->setNeedsSave(false);
|
||||
if (originalFile.exists())
|
||||
originalFile.remove();
|
||||
tempFile.rename(filePath);
|
||||
}
|
||||
|
||||
qint64 elapsedTime = timer.elapsed();
|
||||
qDebug() << "serializing chats took" << elapsedTime << "ms, saved" << nSavedChats << "/" << chats.size() << "chats";
|
||||
qDebug() << "serializing chats took:" << elapsedTime << "ms";
|
||||
emit saveChatsFinished();
|
||||
}
|
||||
|
||||
@@ -228,16 +194,11 @@ void ChatsRestoreThread::run()
|
||||
qint32 version;
|
||||
in >> version;
|
||||
if (version < 1) {
|
||||
qWarning() << "WARNING: Chat file version" << version << "is not supported:" << file.fileName();
|
||||
continue;
|
||||
}
|
||||
if (version > CHAT_FORMAT_VERSION) {
|
||||
qWarning().nospace() << "WARNING: Chat file is from a future version (have " << version << " want "
|
||||
<< CHAT_FORMAT_VERSION << "): " << file.fileName();
|
||||
qWarning() << "ERROR: Chat file has non supported version:" << file.fileName();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (version < 2)
|
||||
if (version <= 1)
|
||||
in.setVersion(QDataStream::Qt_6_2);
|
||||
|
||||
FileInfo info;
|
||||
@@ -278,21 +239,18 @@ void ChatsRestoreThread::run()
|
||||
continue;
|
||||
}
|
||||
|
||||
if (version < 2)
|
||||
if (version <= 1)
|
||||
in.setVersion(QDataStream::Qt_6_2);
|
||||
}
|
||||
|
||||
qDebug() << "deserializing chat" << f.file;
|
||||
|
||||
auto chat = std::make_unique<Chat>();
|
||||
Chat *chat = new Chat;
|
||||
chat->moveToThread(qGuiApp->thread());
|
||||
bool ok = chat->deserialize(in, version);
|
||||
if (!ok) {
|
||||
if (!chat->deserialize(in, version)) {
|
||||
qWarning() << "ERROR: Couldn't deserialize chat from file:" << file.fileName();
|
||||
} else if (!in.atEnd()) {
|
||||
qWarning().nospace() << "error loading chat from " << file.fileName() << ": extra data at end of file";
|
||||
} else {
|
||||
emit chatRestored(chat.release());
|
||||
emit chatRestored(chat);
|
||||
}
|
||||
if (f.oldFile)
|
||||
file.remove(); // No longer storing in this directory
|
||||
|
||||