Compare commits

...

17 Commits

Author SHA1 Message Date
Adam Treat
1bc16a2a4f Bump version to v3.0.0-rc4
Signed-off-by: Adam Treat <treat.adam@gmail.com>
2024-06-29 18:02:45 -04:00
AT
b5fdc4c05a Chatview and combobox UI fixes (#2489)
Chatview and combobox UI fixes (#2489)

Signed-off-by: Adam Treat <treat.adam@gmail.com>
2024-06-29 18:00:52 -04:00
AT
55858f93b0 Solve a bad performance problem in text processing that leads to hangs of the UI. (#2487)
Signed-off-by: Adam Treat <treat.adam@gmail.com>
2024-06-29 17:59:45 -04:00
Adam Treat
720ea5a147 Bump the version to rc3.
Signed-off-by: Adam Treat <treat.adam@gmail.com>
2024-06-28 20:34:17 -04:00
Jared Van Bortel
a191844a3f new UI fixes, part 5 (#2485)
additional new ui changes, part 5 (#2485)

Signed-off-by: Jared Van Bortel <jared@nomic.ai>
2024-06-28 20:34:03 -04:00
Jared Van Bortel
22396a6fa1 AddCollectionView: fix label colors (#2484)
Signed-off-by: Jared Van Bortel <jared@nomic.ai>
2024-06-28 17:50:12 -04:00
Jared Van Bortel
f2cad6abaa additional new ui changes, part 4 (#2481)
Signed-off-by: Jared Van Bortel <jared@nomic.ai>
2024-06-28 17:11:12 -04:00
Adam Treat
d893a6e5d6 Give it an empty string default.
Signed-off-by: Adam Treat <treat.adam@gmail.com>
2024-06-28 14:52:44 -04:00
Adam Treat
8fe73832a6 Fix up the loading default model to display the actual name as per Vincent.
Signed-off-by: Adam Treat <treat.adam@gmail.com>
2024-06-28 14:52:44 -04:00
John W. Parent
23e8b187a4 Add basic signing of app bundle and binaries (#2472)
Adds verification functionality to codesign script
Adds required context to enable XCode to perform the signing
Adds install time check + signing for all binaries
Adds instructions allowing macdeployqt to sign the finalized app bundle

Signed-off-by: John Parent <john.parent@kitware.com>
2024-06-28 14:21:18 -04:00
AT
dc6d01a0bb Change the name of this property to not conflict. (#2480)
Signed-off-by: Adam Treat <treat.adam@gmail.com>
2024-06-28 13:34:26 -04:00
Jared Van Bortel
2c8d634b5b UI and embedding device changes for GPT4All v3.0.0-rc3 (#2477)
Signed-off-by: Jared Van Bortel <jared@nomic.ai>
2024-06-28 12:57:57 -04:00
AT
426aa5eb47 Go ahead and try to handle links in the text by opening them externally. (#2479)
Handle links in the text by opening them externally.

Signed-off-by: Adam Treat <treat.adam@gmail.com>
2024-06-28 12:54:12 -04:00
Jared Van Bortel
81bbeef5b3 partially back out that change, I wasn't reading
Signed-off-by: Jared Van Bortel <jared@nomic.ai>
2024-06-28 11:49:12 -04:00
Jared Van Bortel
1712830228 chatviewtextprocessor: fix missing #include and simplify sort
Signed-off-by: Jared Van Bortel <jared@nomic.ai>
2024-06-28 11:49:12 -04:00
Adam Treat
f6f265f968 This allows support for markdown table display and <foo>
Signed-off-by: Adam Treat <treat.adam@gmail.com>
2024-06-28 11:49:12 -04:00
Adam Treat
6f52f602ef Improved markdown support:
* Correctly displays inline code blocks with syntax highlighting turned on
as well as markdown at the same time
* Adds a context menu item for toggling markdown on and off which also
which essentially turns on/off all text processing
* Uses QTextDocument::MarkdownNoHTML to handle markdown in QTextDocument
which allows display of html tags like normal, but unfortunately does not
allow display of markdown tables as markdown

Signed-off-by: Adam Treat <treat.adam@gmail.com>
2024-06-28 11:49:12 -04:00
54 changed files with 1635 additions and 962 deletions

View File

@@ -56,6 +56,15 @@ jobs:
key: macos-qt-cache-v3
paths:
- ~/Qt
- run:
name: Setup Keychain
command: |
echo $MAC_SIGNING_CERT | base64 --decode > cert.p12
security create-keychain -p "$MAC_KEYCHAIN_KEY" sign.keychain
security default-keychain -s sign.keychain
security unlock-keychain -p "$MAC_KEYCHAIN_KEY" sign.keychain
security import cert.p12 -k sign.keychain -P "$MAC_SIGNING_CERT_PWD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MAC_KEYCHAIN_KEY" sign.keychain
- run:
name: Build
command: |
@@ -67,6 +76,7 @@ jobs:
-DBUILD_UNIVERSAL=ON \
-DMACDEPLOYQT=~/Qt/6.5.1/macos/bin/macdeployqt \
-DGPT4ALL_OFFLINE_INSTALLER=ON \
-DGPT4ALL_SIGN_INSTALL=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH:PATH=~/Qt/6.5.1/macos/lib/cmake/Qt6 \
-DCMAKE_MAKE_PROGRAM:FILEPATH=~/Qt/Tools/Ninja/ninja \

View File

@@ -20,7 +20,7 @@ set(APP_VERSION_MAJOR 3)
set(APP_VERSION_MINOR 0)
set(APP_VERSION_PATCH 0)
set(APP_VERSION_BASE "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}")
set(APP_VERSION "${APP_VERSION_BASE}-rc2")
set(APP_VERSION "${APP_VERSION_BASE}-rc4")
# Include the binary directory for the generated header file
include_directories("${CMAKE_CURRENT_BINARY_DIR}")
@@ -33,6 +33,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
option(GPT4ALL_LOCALHOST OFF "Build installer for localhost repo")
option(GPT4ALL_OFFLINE_INSTALLER "Build an offline installer" OFF)
option(GPT4ALL_SIGN_INSTALL "Sign installed binaries and installers (requires signing identities)" OFF)
# Generate a header file with the version number
configure_file(
@@ -109,6 +110,7 @@ qt_add_executable(chat
chatllm.h chatllm.cpp
chatmodel.h chatlistmodel.h chatlistmodel.cpp
chatapi.h chatapi.cpp
chatviewtextprocessor.h chatviewtextprocessor.cpp
database.h database.cpp
download.h download.cpp
embllm.cpp embllm.h
@@ -119,7 +121,6 @@ qt_add_executable(chat
network.h network.cpp
server.h server.cpp
logger.h logger.cpp
responsetext.h responsetext.cpp
${APP_ICON_RESOURCE}
${CHAT_EXE_RESOURCES}
)
@@ -132,39 +133,40 @@ qt_add_qml_module(chat
main.qml
qml/AddCollectionView.qml
qml/AddModelView.qml
qml/ApplicationSettings.qml
qml/ChatDrawer.qml
qml/ChatView.qml
qml/CollectionsDrawer.qml
qml/HomeView.qml
qml/LocalDocsSettings.qml
qml/LocalDocsView.qml
qml/ModelSettings.qml
qml/ModelsView.qml
qml/NetworkDialog.qml
qml/NewVersionDialog.qml
qml/ThumbsDownDialog.qml
qml/PopupDialog.qml
qml/SettingsView.qml
qml/StartupDialog.qml
qml/PopupDialog.qml
qml/Theme.qml
qml/ModelSettings.qml
qml/ApplicationSettings.qml
qml/LocalDocsSettings.qml
qml/LocalDocsView.qml
qml/SwitchModelDialog.qml
qml/MySettingsTab.qml
qml/MySettingsStack.qml
qml/MySettingsDestructiveButton.qml
qml/MySettingsButton.qml
qml/MySettingsLabel.qml
qml/MySlug.qml
qml/Theme.qml
qml/ThumbsDownDialog.qml
qml/MyBusyIndicator.qml
qml/MyButton.qml
qml/MyCheckBox.qml
qml/MyComboBox.qml
qml/MyDialog.qml
qml/MyDirectoryField.qml
qml/MyFancyLink.qml
qml/MyTextArea.qml
qml/MyTextField.qml
qml/MyCheckBox.qml
qml/MyBusyIndicator.qml
qml/MyMiniButton.qml
qml/MySettingsButton.qml
qml/MySettingsDestructiveButton.qml
qml/MySettingsLabel.qml
qml/MySettingsStack.qml
qml/MySettingsTab.qml
qml/MySlug.qml
qml/MyTextArea.qml
qml/MyTextButton.qml
qml/MyTextField.qml
qml/MyToolButton.qml
qml/MyWelcomeButton.qml
RESOURCES
@@ -206,11 +208,12 @@ qt_add_qml_module(chat
icons/thumbs_up.svg
icons/thumbs_down.svg
icons/twitter.svg
icons/up_down.svg
icons/left_panel_closed.svg
icons/left_panel_open.svg
icons/logo.svg
icons/logo-32.png
icons/logo-48.png
icons/gpt4all.svg
icons/gpt4all-32.png
icons/gpt4all-48.png
icons/you.svg
icons/alt_logo.svg
)
@@ -219,6 +222,13 @@ set_target_properties(chat PROPERTIES
WIN32_EXECUTABLE TRUE
)
macro(REPORT_MISSING_SIGNING_CONTEXT)
message(FATAL_ERROR [=[
Signing requested but no identity configured.
Please set the correct env variable or provide the MAC_SIGNING_IDENTITY argument on the command line
]=])
endmacro()
if (APPLE)
set_target_properties(chat PROPERTIES
MACOSX_BUNDLE TRUE
@@ -229,6 +239,28 @@ if (APPLE)
OUTPUT_NAME gpt4all
)
add_dependencies(chat ggml-metal)
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} AND GPT4ALL_SIGN_INSTALL)
REPORT_MISSING_SIGNING_CONTEXT()
endif()
set(MAC_SIGNING_TID $ENV{MAC_NOTARIZATION_TID})
endif()
# Setup MacOS signing for individual binaries
set_target_properties(chat PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE "Manual"
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${MAC_SIGNING_TID}
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ${MAC_SIGNING_IDENTITY}
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED True
XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--timestamp=http://timestamp.apple.com/ts01 --options=runtime,library"
)
endif()
target_compile_definitions(chat
@@ -253,6 +285,10 @@ target_link_libraries(chat
# -- install --
function(install_sign_osx tgt)
install(CODE "execute_process(COMMAND codesign --options runtime --timestamp -s \"${MAC_SIGNING_IDENTITY}\" $<TARGET_FILE:${tgt}>)")
endfunction()
set(COMPONENT_NAME_MAIN ${PROJECT_NAME})
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
@@ -295,6 +331,14 @@ install(
RUNTIME DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .dll
)
if(APPLE AND GPT4ALL_SIGN_INSTALL)
install_sign_osx(chat)
install_sign_osx(llmodel)
foreach(tgt ${MODEL_IMPL_TARGETS})
install_sign_osx(${tgt})
endforeach()
endif()
if (LLMODEL_CUDA)
set_property(TARGET llamamodel-mainline-cuda llamamodel-mainline-cuda-avxonly
APPEND PROPERTY INSTALL_RPATH "$ORIGIN")
@@ -362,7 +406,7 @@ 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://gpt4all.io")
set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/icons/logo-48.png")
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")
@@ -372,8 +416,8 @@ set(CPACK_IFW_PACKAGE_TITLE "GPT4All Installer")
set(CPACK_IFW_PACKAGE_PUBLISHER "Nomic, Inc.")
set(CPACK_IFW_PRODUCT_URL "https://gpt4all.io")
set(CPACK_IFW_PACKAGE_WIZARD_STYLE "Aero")
set(CPACK_IFW_PACKAGE_LOGO "${CMAKE_CURRENT_SOURCE_DIR}/icons/logo-48.png")
set(CPACK_IFW_PACKAGE_WINDOW_ICON "${CMAKE_CURRENT_SOURCE_DIR}/icons/logo-32.png")
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)
include(InstallRequiredSystemLibraries)

View File

@@ -17,7 +17,6 @@
#include <QMutexLocker>
#include <QSet>
#include <QStringList>
#include <QVariantMap>
#include <QWaitCondition>
#include <Qt>
#include <QtLogging>
@@ -340,181 +339,8 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
model->setRequestURL(modelInfo.url());
model->setAPIKey(apiKey);
m_llModelInfo.resetModel(this, model);
} else {
QElapsedTimer modelLoadTimer;
modelLoadTimer.start();
auto requestedDevice = MySettings::globalInstance()->device();
auto n_ctx = MySettings::globalInstance()->modelContextLength(modelInfo);
m_ctx.n_ctx = n_ctx;
auto ngl = MySettings::globalInstance()->modelGpuLayers(modelInfo);
std::string backend = "auto";
#ifdef Q_OS_MAC
if (requestedDevice == "CPU") {
backend = "cpu";
} else if (m_forceMetal) {
#ifdef __aarch64__
backend = "metal";
#endif
}
#else // !defined(Q_OS_MAC)
if (requestedDevice.startsWith("CUDA: "))
backend = "cuda";
#endif
QString constructError;
m_llModelInfo.resetModel(this);
try {
auto *model = LLModel::Implementation::construct(filePath.toStdString(), backend, n_ctx);
m_llModelInfo.resetModel(this, model);
} catch (const LLModel::MissingImplementationError &e) {
modelLoadProps.insert("error", "missing_model_impl");
constructError = e.what();
} catch (const LLModel::UnsupportedModelError &e) {
modelLoadProps.insert("error", "unsupported_model_file");
constructError = e.what();
} catch (const LLModel::BadArchError &e) {
constructError = e.what();
modelLoadProps.insert("error", "unsupported_model_arch");
modelLoadProps.insert("model_arch", QString::fromStdString(e.arch()));
}
if (m_llModelInfo.model) {
if (m_llModelInfo.model->isModelBlacklisted(filePath.toStdString())) {
static QSet<QString> warned;
auto fname = modelInfo.filename();
if (!warned.contains(fname)) {
emit modelLoadingWarning(
u"%1 is known to be broken. Please get a replacement via the download dialog."_s.arg(fname)
);
warned.insert(fname); // don't warn again until restart
}
}
m_llModelInfo.model->setProgressCallback([this](float progress) -> bool {
progress = std::max(progress, std::numeric_limits<float>::min()); // keep progress above zero
emit modelLoadingPercentageChanged(progress);
return m_shouldBeLoaded;
});
auto approxDeviceMemGB = [](const LLModel::GPUDevice *dev) {
float memGB = dev->heapSize / float(1024 * 1024 * 1024);
return std::floor(memGB * 10.f) / 10.f; // truncate to 1 decimal place
};
std::vector<LLModel::GPUDevice> availableDevices;
const LLModel::GPUDevice *defaultDevice = nullptr;
{
const size_t requiredMemory = m_llModelInfo.model->requiredMem(filePath.toStdString(), n_ctx, ngl);
availableDevices = m_llModelInfo.model->availableGPUDevices(requiredMemory);
// Pick the best device
// NB: relies on the fact that Kompute devices are listed first
if (!availableDevices.empty() && availableDevices.front().type == 2 /*a discrete gpu*/) {
defaultDevice = &availableDevices.front();
float memGB = defaultDevice->heapSize / float(1024 * 1024 * 1024);
memGB = std::floor(memGB * 10.f) / 10.f; // truncate to 1 decimal place
modelLoadProps.insert("default_device", QString::fromStdString(defaultDevice->name));
modelLoadProps.insert("default_device_mem", approxDeviceMemGB(defaultDevice));
modelLoadProps.insert("default_device_backend", QString::fromStdString(defaultDevice->backendName()));
}
}
bool actualDeviceIsCPU = true;
#if defined(Q_OS_MAC) && defined(__aarch64__)
if (m_llModelInfo.model->implementation().buildVariant() == "metal")
actualDeviceIsCPU = false;
#else
if (requestedDevice != "CPU") {
const auto *device = defaultDevice;
if (requestedDevice != "Auto") {
// Use the selected device
for (const LLModel::GPUDevice &d : availableDevices) {
if (QString::fromStdString(d.selectionName()) == requestedDevice) {
device = &d;
break;
}
}
}
std::string unavail_reason;
if (!device) {
// GPU not available
} else if (!m_llModelInfo.model->initializeGPUDevice(device->index, &unavail_reason)) {
m_llModelInfo.fallbackReason = QString::fromStdString(unavail_reason);
} else {
actualDeviceIsCPU = false;
modelLoadProps.insert("requested_device_mem", approxDeviceMemGB(device));
}
}
#endif
// Report which device we're actually using
bool success = m_llModelInfo.model->loadModel(filePath.toStdString(), n_ctx, ngl);
if (!m_shouldBeLoaded) {
m_llModelInfo.resetModel(this);
if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo));
resetModel();
emit modelLoadingPercentageChanged(0.0f);
return false;
}
if (actualDeviceIsCPU) {
// we asked llama.cpp to use the CPU
} else if (!success) {
// llama_init_from_file returned nullptr
m_llModelInfo.fallbackReason = "GPU loading failed (out of VRAM?)";
modelLoadProps.insert("cpu_fallback_reason", "gpu_load_failed");
success = m_llModelInfo.model->loadModel(filePath.toStdString(), n_ctx, 0);
if (!m_shouldBeLoaded) {
m_llModelInfo.resetModel(this);
if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo));
resetModel();
emit modelLoadingPercentageChanged(0.0f);
return false;
}
} else if (!m_llModelInfo.model->usingGPUDevice()) {
// ggml_vk_init was not called in llama.cpp
// We might have had to fallback to CPU after load if the model is not possible to accelerate
// for instance if the quantization method is not supported on Vulkan yet
m_llModelInfo.fallbackReason = "model or quant has no GPU support";
modelLoadProps.insert("cpu_fallback_reason", "gpu_unsupported_model");
}
if (!success) {
m_llModelInfo.resetModel(this);
if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo));
resetModel();
emit modelLoadingError(u"Could not load model due to invalid model file for %1"_s.arg(modelInfo.filename()));
modelLoadProps.insert("error", "loadmodel_failed");
} else {
switch (m_llModelInfo.model->implementation().modelType()[0]) {
case 'L': m_llModelType = LLModelType::LLAMA_; break;
case 'G': m_llModelType = LLModelType::GPTJ_; break;
default:
{
m_llModelInfo.resetModel(this);
if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo));
resetModel();
emit modelLoadingError(u"Could not determine model type for %1"_s.arg(modelInfo.filename()));
}
}
modelLoadProps.insert("$duration", modelLoadTimer.elapsed() / 1000.);
}
} else {
if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo));
resetModel();
emit modelLoadingError(u"Error loading %1: %2"_s.arg(modelInfo.filename(), constructError));
}
} else if (!loadNewModel(modelInfo, modelLoadProps)) {
return false; // m_shouldBeLoaded became false
}
#if defined(DEBUG_MODEL_LOADING)
qDebug() << "new model" << m_llmThread.objectName() << m_llModelInfo.model.get();
@@ -544,6 +370,201 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo)
return bool(m_llModelInfo.model);
}
/* Returns false if the model should no longer be loaded (!m_shouldBeLoaded).
* Otherwise returns true, even on error. */
bool ChatLLM::loadNewModel(const ModelInfo &modelInfo, QVariantMap &modelLoadProps)
{
QElapsedTimer modelLoadTimer;
modelLoadTimer.start();
QString requestedDevice = MySettings::globalInstance()->device();
int n_ctx = MySettings::globalInstance()->modelContextLength(modelInfo);
m_ctx.n_ctx = n_ctx;
int ngl = MySettings::globalInstance()->modelGpuLayers(modelInfo);
std::string backend = "auto";
#ifdef Q_OS_MAC
if (requestedDevice == "CPU") {
backend = "cpu";
} else if (m_forceMetal) {
#ifdef __aarch64__
backend = "metal";
#endif
}
#else // !defined(Q_OS_MAC)
if (requestedDevice.startsWith("CUDA: "))
backend = "cuda";
#endif
QString filePath = modelInfo.dirpath + modelInfo.filename();
auto construct = [this, &filePath, &modelInfo, &modelLoadProps, n_ctx](std::string const &backend) {
QString constructError;
m_llModelInfo.resetModel(this);
try {
auto *model = LLModel::Implementation::construct(filePath.toStdString(), backend, n_ctx);
m_llModelInfo.resetModel(this, model);
} catch (const LLModel::MissingImplementationError &e) {
modelLoadProps.insert("error", "missing_model_impl");
constructError = e.what();
} catch (const LLModel::UnsupportedModelError &e) {
modelLoadProps.insert("error", "unsupported_model_file");
constructError = e.what();
} catch (const LLModel::BadArchError &e) {
constructError = e.what();
modelLoadProps.insert("error", "unsupported_model_arch");
modelLoadProps.insert("model_arch", QString::fromStdString(e.arch()));
}
if (!m_llModelInfo.model) {
if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo));
resetModel();
emit modelLoadingError(u"Error loading %1: %2"_s.arg(modelInfo.filename(), constructError));
return false;
}
m_llModelInfo.model->setProgressCallback([this](float progress) -> bool {
progress = std::max(progress, std::numeric_limits<float>::min()); // keep progress above zero
emit modelLoadingPercentageChanged(progress);
return m_shouldBeLoaded;
});
return true;
};
if (!construct(backend))
return true;
if (m_llModelInfo.model->isModelBlacklisted(filePath.toStdString())) {
static QSet<QString> warned;
auto fname = modelInfo.filename();
if (!warned.contains(fname)) {
emit modelLoadingWarning(
u"%1 is known to be broken. Please get a replacement via the download dialog."_s.arg(fname)
);
warned.insert(fname); // don't warn again until restart
}
}
auto approxDeviceMemGB = [](const LLModel::GPUDevice *dev) {
float memGB = dev->heapSize / float(1024 * 1024 * 1024);
return std::floor(memGB * 10.f) / 10.f; // truncate to 1 decimal place
};
std::vector<LLModel::GPUDevice> availableDevices;
const LLModel::GPUDevice *defaultDevice = nullptr;
{
const size_t requiredMemory = m_llModelInfo.model->requiredMem(filePath.toStdString(), n_ctx, ngl);
availableDevices = m_llModelInfo.model->availableGPUDevices(requiredMemory);
// Pick the best device
// NB: relies on the fact that Kompute devices are listed first
if (!availableDevices.empty() && availableDevices.front().type == 2 /*a discrete gpu*/) {
defaultDevice = &availableDevices.front();
float memGB = defaultDevice->heapSize / float(1024 * 1024 * 1024);
memGB = std::floor(memGB * 10.f) / 10.f; // truncate to 1 decimal place
modelLoadProps.insert("default_device", QString::fromStdString(defaultDevice->name));
modelLoadProps.insert("default_device_mem", approxDeviceMemGB(defaultDevice));
modelLoadProps.insert("default_device_backend", QString::fromStdString(defaultDevice->backendName()));
}
}
bool actualDeviceIsCPU = true;
#if defined(Q_OS_MAC) && defined(__aarch64__)
if (m_llModelInfo.model->implementation().buildVariant() == "metal")
actualDeviceIsCPU = false;
#else
if (requestedDevice != "CPU") {
const auto *device = defaultDevice;
if (requestedDevice != "Auto") {
// Use the selected device
for (const LLModel::GPUDevice &d : availableDevices) {
if (QString::fromStdString(d.selectionName()) == requestedDevice) {
device = &d;
break;
}
}
}
std::string unavail_reason;
if (!device) {
// GPU not available
} else if (!m_llModelInfo.model->initializeGPUDevice(device->index, &unavail_reason)) {
m_llModelInfo.fallbackReason = QString::fromStdString(unavail_reason);
} else {
actualDeviceIsCPU = false;
modelLoadProps.insert("requested_device_mem", approxDeviceMemGB(device));
}
}
#endif
bool success = m_llModelInfo.model->loadModel(filePath.toStdString(), n_ctx, ngl);
if (!m_shouldBeLoaded) {
m_llModelInfo.resetModel(this);
if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo));
resetModel();
emit modelLoadingPercentageChanged(0.0f);
return false;
}
if (actualDeviceIsCPU) {
// we asked llama.cpp to use the CPU
} else if (!success) {
// llama_init_from_file returned nullptr
m_llModelInfo.fallbackReason = "GPU loading failed (out of VRAM?)";
modelLoadProps.insert("cpu_fallback_reason", "gpu_load_failed");
// For CUDA, make sure we don't use the GPU at all - ngl=0 still offloads matmuls
if (backend == "cuda" && !construct("auto"))
return true;
success = m_llModelInfo.model->loadModel(filePath.toStdString(), n_ctx, 0);
if (!m_shouldBeLoaded) {
m_llModelInfo.resetModel(this);
if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo));
resetModel();
emit modelLoadingPercentageChanged(0.0f);
return false;
}
} else if (!m_llModelInfo.model->usingGPUDevice()) {
// ggml_vk_init was not called in llama.cpp
// We might have had to fallback to CPU after load if the model is not possible to accelerate
// for instance if the quantization method is not supported on Vulkan yet
m_llModelInfo.fallbackReason = "model or quant has no GPU support";
modelLoadProps.insert("cpu_fallback_reason", "gpu_unsupported_model");
}
if (!success) {
m_llModelInfo.resetModel(this);
if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo));
resetModel();
emit modelLoadingError(u"Could not load model due to invalid model file for %1"_s.arg(modelInfo.filename()));
modelLoadProps.insert("error", "loadmodel_failed");
return true;
}
switch (m_llModelInfo.model->implementation().modelType()[0]) {
case 'L': m_llModelType = LLModelType::LLAMA_; break;
case 'G': m_llModelType = LLModelType::GPTJ_; break;
default:
{
m_llModelInfo.resetModel(this);
if (!m_isServer)
LLModelStore::globalInstance()->releaseModel(std::move(m_llModelInfo));
resetModel();
emit modelLoadingError(u"Could not determine model type for %1"_s.arg(modelInfo.filename()));
}
}
modelLoadProps.insert("$duration", modelLoadTimer.elapsed() / 1000.);
return true;
};
bool ChatLLM::isModelLoaded() const
{
return m_llModelInfo.model && m_llModelInfo.model->isModelLoaded();

View File

@@ -14,6 +14,7 @@
#include <QPair>
#include <QString>
#include <QThread>
#include <QVariantMap>
#include <QVector>
#include <QtGlobal>
@@ -214,6 +215,8 @@ protected:
quint32 m_promptResponseTokens;
private:
bool loadNewModel(const ModelInfo &modelInfo, QVariantMap &modelLoadProps);
std::string m_response;
std::string m_nameResponse;
LLModelInfo m_llModelInfo;

View File

@@ -1,4 +1,4 @@
#include "responsetext.h"
#include "chatviewtextprocessor.h"
#include <QBrush>
#include <QChar>
@@ -23,6 +23,8 @@
#include <Qt>
#include <QtGlobal>
#include <algorithm>
enum Language {
None,
Python,
@@ -35,7 +37,8 @@ enum Language {
Csharp,
Latex,
Html,
Php
Php,
Markdown
};
// TODO (Adam) These should be themeable and not hardcoded since they are quite harsh on the eyes in
@@ -827,29 +830,40 @@ void SyntaxHighlighter::highlightBlock(const QString &text)
{
QTextBlock block = this->currentBlock();
// Search the first block of the frame we're in for the code to use for highlighting
int userState = block.userState();
if (QTextFrame *frame = block.document()->frameAt(block.position())) {
QTextBlock firstBlock = frame->begin().currentBlock();
if (firstBlock.isValid())
userState = firstBlock.userState();
}
QVector<HighlightingRule> rules;
if (block.userState() == Python)
rules = pythonHighlightingRules();
else if (block.userState() == Cpp)
rules = cppHighlightingRules();
else if (block.userState() == Csharp)
rules = csharpHighlightingRules();
else if (block.userState() == Bash)
rules = bashHighlightingRules();
else if (block.userState() == TypeScript)
rules = typescriptHighlightingRules();
else if (block.userState() == Java)
rules = javaHighlightingRules();
else if (block.userState() == Go)
rules = javaHighlightingRules();
else if (block.userState() == Json)
rules = jsonHighlightingRules();
else if (block.userState() == Latex)
rules = latexHighlightingRules();
else if (block.userState() == Html)
rules = htmlHighlightingRules();
else if (block.userState() == Php)
rules = phpHighlightingRules();
switch (userState) {
case Python:
rules = pythonHighlightingRules(); break;
case Cpp:
rules = cppHighlightingRules(); break;
case Csharp:
rules = csharpHighlightingRules(); break;
case Bash:
rules = bashHighlightingRules(); break;
case TypeScript:
rules = typescriptHighlightingRules(); break;
case Java:
rules = javaHighlightingRules(); break;
case Go:
rules = javaHighlightingRules(); break;
case Json:
rules = jsonHighlightingRules(); break;
case Latex:
rules = latexHighlightingRules(); break;
case Html:
rules = htmlHighlightingRules(); break;
case Php:
rules = phpHighlightingRules(); break;
default: break;
}
for (const HighlightingRule &rule : std::as_const(rules)) {
QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
@@ -868,31 +882,32 @@ void SyntaxHighlighter::highlightBlock(const QString &text)
// not the replaced text. A possible solution is to have this class keep a mapping of the original
// indices and the replacement indices and then use the original text that is stored in memory in the
// chat class to populate the clipboard.
ResponseText::ResponseText(QObject *parent)
ChatViewTextProcessor::ChatViewTextProcessor(QObject *parent)
: QObject{parent}
, m_textDocument(nullptr)
, m_syntaxHighlighter(new SyntaxHighlighter(this))
, m_isProcessingText(false)
, m_shouldProcessText(true)
{
}
QQuickTextDocument* ResponseText::textDocument() const
QQuickTextDocument* ChatViewTextProcessor::textDocument() const
{
return m_textDocument;
}
void ResponseText::setTextDocument(QQuickTextDocument* textDocument)
void ChatViewTextProcessor::setTextDocument(QQuickTextDocument* textDocument)
{
if (m_textDocument)
disconnect(m_textDocument->textDocument(), &QTextDocument::contentsChanged, this, &ResponseText::handleTextChanged);
disconnect(m_textDocument->textDocument(), &QTextDocument::contentsChanged, this, &ChatViewTextProcessor::handleTextChanged);
m_textDocument = textDocument;
m_syntaxHighlighter->setDocument(m_textDocument->textDocument());
connect(m_textDocument->textDocument(), &QTextDocument::contentsChanged, this, &ResponseText::handleTextChanged);
connect(m_textDocument->textDocument(), &QTextDocument::contentsChanged, this, &ChatViewTextProcessor::handleTextChanged);
handleTextChanged();
}
bool ResponseText::tryCopyAtPosition(int position) const
bool ChatViewTextProcessor::tryCopyAtPosition(int position) const
{
for (const auto &copy : m_copies) {
if (position >= copy.startPos && position <= copy.endPos) {
@@ -904,9 +919,56 @@ bool ResponseText::tryCopyAtPosition(int position) const
return false;
}
void ResponseText::handleTextChanged()
bool ChatViewTextProcessor::shouldProcessText() const
{
if (!m_textDocument || m_isProcessingText)
return m_shouldProcessText;
}
void ChatViewTextProcessor::setShouldProcessText(bool b)
{
if (m_shouldProcessText == b)
return;
m_shouldProcessText = b;
emit shouldProcessTextChanged();
handleTextChanged();
}
void traverseDocument(QTextDocument *doc, QTextFrame *frame)
{
QTextFrame *rootFrame = frame ? frame : doc->rootFrame();
QTextFrame::iterator rootIt;
if (!frame)
qDebug() << "Begin traverse";
for (rootIt = rootFrame->begin(); !rootIt.atEnd(); ++rootIt) {
QTextFrame *childFrame = rootIt.currentFrame();
QTextBlock childBlock = rootIt.currentBlock();
if (childFrame) {
qDebug() << "Frame from" << childFrame->firstPosition() << "to" << childFrame->lastPosition();
traverseDocument(doc, childFrame);
} else if (childBlock.isValid()) {
qDebug() << QString(" Block %1 position:").arg(childBlock.userState()) << childBlock.position();
qDebug() << QString(" Block %1 text:").arg(childBlock.userState()) << childBlock.text();
// Iterate over lines within the block
for (QTextBlock::iterator blockIt = childBlock.begin(); !(blockIt.atEnd()); ++blockIt) {
QTextFragment fragment = blockIt.fragment();
if (fragment.isValid()) {
qDebug() << " Fragment text:" << fragment.text();
}
}
}
}
if (!frame)
qDebug() << "End traverse";
}
void ChatViewTextProcessor::handleTextChanged()
{
if (!m_textDocument || m_isProcessingText || !m_shouldProcessText)
return;
m_isProcessingText = true;
@@ -917,6 +979,7 @@ void ResponseText::handleTextChanged()
(void)doc->documentLayout()->documentSize();
handleCodeBlocks();
handleMarkdown();
// We insert an invisible char at the end to make sure the document goes back to the default
// text format
@@ -926,18 +989,7 @@ void ResponseText::handleTextChanged()
m_isProcessingText = false;
}
void replaceAndInsertMarkdown(int startIndex, int endIndex, QTextDocument *doc)
{
QTextCursor cursor(doc);
cursor.setPosition(startIndex);
cursor.setPosition(endIndex, QTextCursor::KeepAnchor);
QTextDocumentFragment fragment(cursor);
const QString plainText = fragment.toPlainText();
cursor.removeSelectedText();
cursor.insertMarkdown(plainText);
}
void ResponseText::handleCodeBlocks()
void ChatViewTextProcessor::handleCodeBlocks()
{
QTextDocument* doc = m_textDocument->textDocument();
QTextCursor cursor(doc);
@@ -997,21 +1049,15 @@ void ResponseText::handleCodeBlocks()
matchesCode.append(iCode.next());
QVector<CodeCopy> newCopies;
// Track the position in the document to handle non-code blocks
int lastIndex = 0;
QVector<QTextFrame*> frames;
for(int index = matchesCode.count() - 1; index >= 0; --index) {
int nonCodeStart = lastIndex;
int nonCodeEnd = matchesCode[index].capturedStart();
if (nonCodeEnd > nonCodeStart)
replaceAndInsertMarkdown(nonCodeStart, nonCodeEnd, doc);
cursor.setPosition(matchesCode[index].capturedStart());
cursor.setPosition(matchesCode[index].capturedEnd(), QTextCursor::KeepAnchor);
cursor.removeSelectedText();
int startPos = cursor.position();
QTextFrameFormat frameFormat = frameFormatBase;
QString capturedText = matchesCode[index].captured(1);
QString codeLanguage;
@@ -1090,22 +1136,72 @@ void ResponseText::handleCodeBlocks()
codeCursor.setCharFormat(codeBlockCharFormat);
codeCursor.block().setUserState(stringToLanguage(codeLanguage));
for (int i = 0; i < lines.size(); ++i) {
codeCursor.insertText(lines[i]);
if (i < lines.size() - 1) {
codeCursor.insertBlock();
codeCursor.block().setUserState(stringToLanguage(codeLanguage));
}
}
codeCursor.insertText(lines.join('\n'));
cursor = mainFrame->lastCursorPosition();
cursor.setCharFormat(QTextCharFormat());
lastIndex = matchesCode[index].capturedEnd();
}
if (lastIndex < doc->characterCount())
replaceAndInsertMarkdown(lastIndex, doc->characterCount() - 1, doc);
m_copies = newCopies;
}
void replaceAndInsertMarkdown(int startIndex, int endIndex, QTextDocument *doc)
{
QTextCursor cursor(doc);
cursor.setPosition(startIndex);
cursor.setPosition(endIndex, QTextCursor::KeepAnchor);
QTextDocumentFragment fragment(cursor);
const QString plainText = fragment.toPlainText();
cursor.removeSelectedText();
QTextDocument::MarkdownFeatures features = static_cast<QTextDocument::MarkdownFeatures>(
QTextDocument::MarkdownNoHTML | QTextDocument::MarkdownDialectGitHub);
cursor.insertMarkdown(plainText, features);
cursor.block().setUserState(Markdown);
}
void ChatViewTextProcessor::handleMarkdown()
{
QTextDocument* doc = m_textDocument->textDocument();
QTextCursor cursor(doc);
QVector<QPair<int, int>> codeBlockPositions;
QTextFrame *rootFrame = doc->rootFrame();
QTextFrame::iterator rootIt;
bool hasAlreadyProcessedMarkdown = false;
for (rootIt = rootFrame->begin(); !rootIt.atEnd(); ++rootIt) {
QTextFrame *childFrame = rootIt.currentFrame();
QTextBlock childBlock = rootIt.currentBlock();
if (childFrame) {
codeBlockPositions.append(qMakePair(childFrame->firstPosition()-1, childFrame->lastPosition()+1));
for (QTextFrame::iterator frameIt = childFrame->begin(); !frameIt.atEnd(); ++frameIt) {
QTextBlock block = frameIt.currentBlock();
if (block.isValid() && block.userState() == Markdown)
hasAlreadyProcessedMarkdown = true;
}
} else if (childBlock.isValid() && childBlock.userState() == Markdown)
hasAlreadyProcessedMarkdown = true;
}
if (!hasAlreadyProcessedMarkdown) {
std::sort(codeBlockPositions.begin(), codeBlockPositions.end(), [](const QPair<int, int> &a, const QPair<int, int> &b) {
return a.first > b.first;
});
int lastIndex = doc->characterCount() - 1;
for (const auto &pos : codeBlockPositions) {
int nonCodeStart = pos.second;
int nonCodeEnd = lastIndex;
if (nonCodeEnd > nonCodeStart) {
replaceAndInsertMarkdown(nonCodeStart, nonCodeEnd, doc);
}
lastIndex = pos.first;
}
if (lastIndex > 0)
replaceAndInsertMarkdown(0, lastIndex, doc);
}
}

View File

@@ -1,5 +1,5 @@
#ifndef RESPONSETEXT_H
#define RESPONSETEXT_H
#ifndef CHATVIEWTEXTPROCESSOR_H
#define CHATVIEWTEXTPROCESSOR_H
#include <QColor>
#include <QObject>
@@ -37,13 +37,14 @@ struct CodeCopy {
QString text;
};
class ResponseText : public QObject
class ChatViewTextProcessor : public QObject
{
Q_OBJECT
Q_PROPERTY(QQuickTextDocument* textDocument READ textDocument WRITE setTextDocument NOTIFY textDocumentChanged())
Q_PROPERTY(bool shouldProcessText READ shouldProcessText WRITE setShouldProcessText NOTIFY shouldProcessTextChanged())
QML_ELEMENT
public:
explicit ResponseText(QObject *parent = nullptr);
explicit ChatViewTextProcessor(QObject *parent = nullptr);
QQuickTextDocument* textDocument() const;
void setTextDocument(QQuickTextDocument* textDocument);
@@ -53,12 +54,17 @@ public:
Q_INVOKABLE bool tryCopyAtPosition(int position) const;
bool shouldProcessText() const;
void setShouldProcessText(bool b);
Q_SIGNALS:
void textDocumentChanged();
void shouldProcessTextChanged();
private Q_SLOTS:
void handleTextChanged();
void handleCodeBlocks();
void handleMarkdown();
private:
QQuickTextDocument *m_textDocument;
@@ -67,7 +73,8 @@ private:
QVector<CodeCopy> m_copies;
QColor m_linkColor;
QColor m_headerColor;
bool m_shouldProcessText = false;
bool m_isProcessingText = false;
};
#endif // RESPONSETEXT_H
#endif // CHATVIEWTEXTPROCESSOR_H

View File

@@ -6,7 +6,7 @@ set(BIN_DIR ${DATA_DIR}/bin)
set(Qt6_ROOT_DIR "@Qt6_ROOT_DIR@")
set(ENV{LD_LIBRARY_PATH} "${BIN_DIR}:${Qt6_ROOT_DIR}/../lib/")
execute_process(COMMAND ${LINUXDEPLOYQT} ${BIN_DIR}/chat -qmldir=${CMAKE_CURRENT_SOURCE_DIR} -bundle-non-qt-libs -qmake=${Qt6_ROOT_DIR}/bin/qmake -verbose=2 -exclude-libs=libcuda.so.1)
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/icons/logo-32.png"
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/icons/gpt4all-32.png"
DESTINATION ${DATA_DIR})
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/icons/logo-48.png"
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/icons/gpt4all-48.png"
DESTINATION ${DATA_DIR})

View File

@@ -1,7 +1,8 @@
set(MACDEPLOYQT "@MACDEPLOYQT@")
set(COMPONENT_NAME_MAIN "@COMPONENT_NAME_MAIN@")
set(CMAKE_CURRENT_SOURCE_DIR "@CMAKE_CURRENT_SOURCE_DIR@")
execute_process(COMMAND ${MACDEPLOYQT} ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/bin/gpt4all.app -qmldir=${CMAKE_CURRENT_SOURCE_DIR} -verbose=2)
set(GPT4ALL_SIGNING_ID "@MAC_SIGNING_IDENTITY@")
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 MYGPTJLIBS ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/lib/libgptj*)
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.*)
@@ -11,9 +12,9 @@ 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/logo-32.png"
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/logo-48.png"
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)

View File

@@ -2,9 +2,9 @@ set(WINDEPLOYQT "@WINDEPLOYQT@")
set(COMPONENT_NAME_MAIN "@COMPONENT_NAME_MAIN@")
set(CMAKE_CURRENT_SOURCE_DIR "@CMAKE_CURRENT_SOURCE_DIR@")
execute_process(COMMAND ${WINDEPLOYQT} --qmldir ${CMAKE_CURRENT_SOURCE_DIR} ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/bin)
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/icons/logo-32.png"
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/logo-48.png"
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.ico"
DESTINATION ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data)

View File

@@ -44,7 +44,7 @@ Component.prototype.createOperations = function()
homeDir + "/Desktop/GPT4All.desktop",
"Type=Application\nTerminal=false\nExec=\"" + targetDirectory +
"/bin/chat\"\nName=GPT4All\nIcon=" + targetDirectory +
"/logo-48.png\nName[en_US]=GPT4All");
"/gpt4all-48.png\nName[en_US]=GPT4All");
}
}
} catch (e) {

View File

@@ -4,6 +4,7 @@ import subprocess
import tempfile
import shutil
import click
import re
from typing import Optional
# Requires click
@@ -20,7 +21,8 @@ from typing import Optional
@click.option('--output-dmg', required=True, help='Path to the output signed DMG file.')
@click.option('--sha1-hash', help='SHA-1 hash of the Developer ID Application certificate')
@click.option('--signing-identity', default=None, help='Common name of the Developer ID Application certificate')
def sign_dmg(input_dmg: str, output_dmg: str, signing_identity: Optional[str] = None, sha1_hash: Optional[str] = None) -> None:
@click.option('--verify', is_flag=True, show_default=True, required=False, default=False, help='Perform verification of signed app bundle' )
def sign_dmg(input_dmg: str, output_dmg: str, signing_identity: Optional[str] = None, sha1_hash: Optional[str] = None, verify: Optional[bool] = False) -> None:
if not signing_identity and not sha1_hash:
print("Error: Either --signing-identity or --sha1-hash must be provided.")
exit(1)
@@ -64,6 +66,43 @@ def sign_dmg(input_dmg: str, output_dmg: str, signing_identity: Optional[str] =
shutil.rmtree(mount_point)
exit(1)
# Validate signature and entitlements of signed app bundle
if verify:
try:
code_ver_proc = subprocess.run([
'codesign',
'--deep',
'--verify',
'--verbose=2',
'--strict',
app_bundle
], check=True, capture_output=True)
if not re.search(fr"{app_bundle}: valid", code_ver_proc.stdout.decode()):
raise RuntimeError(f"codesign validation failed: {code_ver_proc.stdout.decode()}")
except subprocess.CalledProcessError as e:
print(f"Error during codesign validation: {e}")
# Clean up temporary directories
shutil.rmtree(temp_dir)
shutil.rmtree(mount_point)
exit(1)
try:
spctl_proc = subprocess.run([
'spctl',
'-a',
'-t',
'exec',
'-vv',
app_bundle
], check=True, capture_output=True)
if not re.search(fr"{app_bundle}: accepted", spctl_proc.stdout.decode()):
raise RuntimeError(f"spctl validation failed: {spctl_proc.stdout.decode()}")
except subprocess.CalledProcessError as e:
print(f"Error during spctl validation: {e}")
# Clean up temporary directories
shutil.rmtree(temp_dir)
shutil.rmtree(mount_point)
exit(1)
# Create a new DMG containing the signed .app bundle
subprocess.run([
'hdiutil', 'create',

View File

@@ -1493,7 +1493,7 @@ void Database::start()
} else if (!initDb(modelPath, oldCollections)) {
m_databaseValid = false;
} else {
//cleanDB();
cleanDB();
addCurrentFolders();
}
@@ -2118,7 +2118,8 @@ void Database::changeFileExtensions(const QStringList &extensions)
m_scannedFileExtensions = extensions;
cleanDB();
if (cleanDB())
updateCollectionStatistics();
QSqlQuery q(m_db);
QList<CollectionItem> collections;

View File

@@ -26,6 +26,7 @@
#include <exception>
#include <utility>
#include <vector>
using namespace Qt::Literals::StringLiterals;
@@ -63,9 +64,13 @@ void EmbeddingLLMWorker::wait()
bool EmbeddingLLMWorker::loadModel()
{
constexpr int n_ctx = 2048;
m_nomicAPIKey.clear();
m_model = nullptr;
// TODO(jared): react to setting changes without restarting
if (MySettings::globalInstance()->localDocsUseRemoteEmbed()) {
m_nomicAPIKey = MySettings::globalInstance()->localDocsNomicAPIKey();
return true;
@@ -79,29 +84,86 @@ bool EmbeddingLLMWorker::loadModel()
QString filePath = embPathFmt.arg(QCoreApplication::applicationDirPath(), LOCAL_EMBEDDING_MODEL);
if (!QFileInfo::exists(filePath)) {
qWarning() << "WARNING: Local embedding model not found";
qWarning() << "embllm WARNING: Local embedding model not found";
return false;
}
QString requestedDevice = MySettings::globalInstance()->localDocsEmbedDevice();
std::string backend = "auto";
#ifdef Q_OS_MAC
if (requestedDevice == "Auto" || requestedDevice == "CPU")
backend = "cpu";
#else
if (requestedDevice.startsWith("CUDA: "))
backend = "cuda";
#endif
try {
m_model = LLModel::Implementation::construct(filePath.toStdString());
m_model = LLModel::Implementation::construct(filePath.toStdString(), backend, n_ctx);
} catch (const std::exception &e) {
qWarning() << "WARNING: Could not load embedding model:" << e.what();
qWarning() << "embllm WARNING: Could not load embedding model:" << e.what();
return false;
}
// NOTE: explicitly loads model on CPU to avoid GPU OOM
// TODO(cebtenzzre): support GPU-accelerated embeddings
bool success = m_model->loadModel(filePath.toStdString(), 2048, 0);
bool actualDeviceIsCPU = true;
#if defined(Q_OS_MAC) && defined(__aarch64__)
if (m_model->implementation().buildVariant() == "metal")
actualDeviceIsCPU = false;
#else
if (requestedDevice != "CPU") {
const LLModel::GPUDevice *device = nullptr;
std::vector<LLModel::GPUDevice> availableDevices = m_model->availableGPUDevices(0);
if (requestedDevice != "Auto") {
// Use the selected device
for (const LLModel::GPUDevice &d : availableDevices) {
if (QString::fromStdString(d.selectionName()) == requestedDevice) {
device = &d;
break;
}
}
}
std::string unavail_reason;
if (!device) {
// GPU not available
} else if (!m_model->initializeGPUDevice(device->index, &unavail_reason)) {
qWarning().noquote() << "embllm WARNING: Did not use GPU:" << QString::fromStdString(unavail_reason);
} else {
actualDeviceIsCPU = false;
}
}
#endif
bool success = m_model->loadModel(filePath.toStdString(), n_ctx, 100);
// CPU fallback
if (!actualDeviceIsCPU && !success) {
// llama_init_from_file returned nullptr
qWarning() << "embllm WARNING: Did not use GPU: GPU loading failed (out of VRAM?)";
if (backend == "cuda") {
// For CUDA, make sure we don't use the GPU at all - ngl=0 still offloads matmuls
try {
m_model = LLModel::Implementation::construct(filePath.toStdString(), "auto", n_ctx);
} catch (const std::exception &e) {
qWarning() << "embllm WARNING: Could not load embedding model:" << e.what();
return false;
}
}
success = m_model->loadModel(filePath.toStdString(), n_ctx, 0);
}
if (!success) {
qWarning() << "WARNING: Could not load embedding model";
qWarning() << "embllm WARNING: Could not load embedding model";
delete m_model;
m_model = nullptr;
return false;
}
if (!m_model->supportsEmbedding()) {
qWarning() << "WARNING: Model type does not support embeddings";
qWarning() << "embllm WARNING: Model type does not support embeddings";
delete m_model;
m_model = nullptr;
return false;
@@ -128,7 +190,7 @@ std::vector<float> EmbeddingLLMWorker::generateQueryEmbedding(const QString &tex
std::vector<float> embedding(m_model->embeddingSize());
try {
m_model->embed({text.toStdString()}, embedding.data(), true);
m_model->embed({text.toStdString()}, embedding.data(), /*isRetrieval*/ true);
} catch (const std::exception &e) {
qWarning() << "WARNING: LLModel::embed failed:" << e.what();
return {};
@@ -203,26 +265,34 @@ void EmbeddingLLMWorker::docEmbeddingsRequested(const QVector<EmbeddingChunk> &c
if (!isNomic) {
QVector<EmbeddingResult> results;
results.reserve(chunks.size());
std::vector<std::string> texts;
texts.reserve(chunks.size());
for (const auto &c: chunks) {
EmbeddingResult result;
result.model = c.model;
result.folder_id = c.folder_id;
result.chunk_id = c.chunk_id;
// TODO(cebtenzzre): take advantage of batched embeddings
result.embedding.resize(m_model->embeddingSize());
{
QMutexLocker locker(&m_mutex);
try {
m_model->embed({c.chunk.toStdString()}, result.embedding.data(), false);
} catch (const std::exception &e) {
qWarning() << "WARNING: LLModel::embed failed:" << e.what();
return;
}
}
results << result;
texts.push_back(c.chunk.toStdString());
}
constexpr int BATCH_SIZE = 4;
std::vector<float> result;
result.resize(chunks.size() * m_model->embeddingSize());
for (int j = 0; j < chunks.size(); j += BATCH_SIZE) {
QMutexLocker locker(&m_mutex);
std::vector batchTexts(texts.begin() + j, texts.begin() + std::min(j + BATCH_SIZE, int(texts.size())));
try {
m_model->embed(batchTexts, result.data() + j * m_model->embeddingSize(), /*isRetrieval*/ false);
} catch (const std::exception &e) {
qWarning() << "WARNING: LLModel::embed failed:" << e.what();
return;
}
}
for (int i = 0; i < chunks.size(); i++)
memcpy(results[i].embedding.data(), &result[i * m_model->embeddingSize()], m_model->embeddingSize() * sizeof(float));
emit embeddingsGenerated(results);
return;
};

View File

@@ -1,6 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#7d7d8e" viewBox="0 0 448 512"><path d="M448 384v64c0 17.673-14.327 32-32 32H32c-17.673 0-32-14.327-32-32v-64c0-17.673 14.327-32 32-32h384c17.673 0 32 14.327 32 32zM48.053 320h351.886c41.651 0 63.581-49.674 35.383-80.435L259.383 47.558c-19.014-20.743-51.751-20.744-70.767 0L12.67 239.565C-15.475 270.268 6.324 320 48.053 320z"/></svg>
<!--
Font Awesome Free 5.2.0 by @fontawesome - https://fontawesome.com
License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
-->
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M232,200a8,8,0,0,1-8,8H32a8,8,0,1,1,0-16H224A8,8,0,0,1,232,200ZM25.59,150.84a16,16,0,0,1,2-17.07L109.26,32.94a24.11,24.11,0,0,1,37.48,0l81.65,100.83A16.1,16.1,0,0,1,215.91,160H40.09A16,16,0,0,1,25.59,150.84ZM40,143.91s0,.09.08.11l175.83,0s.08-.09.08-.13L134.3,43a8.1,8.1,0,0,0-12.6,0L40,143.84A.28.28,0,0,0,40,143.91Z"></path></svg>

Before

Width:  |  Height:  |  Size: 557 B

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,34 @@
<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(.8125 0 0 .8125 6 6)">
<rect width="64" height="64" rx="14.154" ry="14.154" fill="#f8f6f6"/>
<path d="m26.348 12.651c-0.716-1.4831 1.779-5.6511 5.6548-5.6511 3.8721 0 5.9433 3.9854 5.6547 5.6548-3.3351-1.4831-7.3607-1.8119-11.306 0l-0.0036-0.0037z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m44.398 15.902c0.716-1.4867 5.5306-2.1333 7.9452 0.895s0.5918 7.1342-0.895 7.9452c-0.9205-3.5324-3.1708-6.8858-7.0502-8.8402z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m53.114 32.037c1.6073-0.3689 5.1141 2.9918 4.252 6.7689-0.8621 3.7772-5.2091 4.9096-6.7689 4.2521 2.1881-2.9224 3.4045-6.7726 2.5169-11.021z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m45.932 48.914c1.2895 1.0264 0.8511 5.8666-2.6411 7.547-3.4923 1.6803-7.0868-1.0119-7.547-2.6411 3.6493-0.1096 7.4191-1.5598 10.188-4.9059z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m28.259 53.82c0 1.6475-4.0548 4.3214-7.547 2.6411-3.4886-1.6804-3.6274-6.1699-2.6411-7.547 2.3598 2.7835 5.8447 4.8292 10.188 4.9059z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m13.406 43.062c-1.2895 1.0301-5.9068-0.4785-6.7689-4.252-0.8621-3.7772 2.5607-6.6813 4.252-6.769-0.705 3.5836-0.1315 7.5799 2.5169 11.021z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m12.555 24.739c-1.6073-0.3653-3.3096-4.9169-0.895-7.9452s6.8237-2.1626 7.9452-0.895c-3.2402 1.684-6.0055 4.6246-7.0502 8.8402z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m31.999 35.646c1.4526 0 2.6301-1.1776 2.6301-2.6302s-1.1775-2.6301-2.6301-2.6301-2.6302 1.1775-2.6302 2.6301 1.1776 2.6302 2.6302 2.6302z" fill="#436544"/>
<path d="m36.661 49.798c0.5918 1.2237-1.4648 4.6575-4.6575 4.6575s-4.8987-3.284-4.6576-4.6575c2.7471 1.2237 6.064 1.4941 9.3151 0z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m21.786 47.124c-0.5881 1.2238-4.5552 1.7571-6.5461-0.7379-1.9909-2.4949-0.4858-5.8776 0.7379-6.5461 0.7562 2.9114 2.6119 5.6731 5.8082 7.284z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m14.604 33.827c-1.3261 0.3032-4.2156-2.4658-3.5032-5.5781 0.7123-3.1123 4.2922-4.0438 5.578-3.5032-1.8045 2.4073-2.8054 5.5817-2.0748 9.0813z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m20.522 19.921c-1.063-0.8475-0.7013-4.8328 2.1772-6.2173s5.8374 0.8329 6.2173 2.1771c-3.0064 0.0914-6.1114 1.2859-8.3945 4.0439z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m35.086 15.877c0-1.3589 3.3424-3.5617 6.2173-2.1772s2.9881 5.0849 2.1772 6.2174c-1.947-2.2941-4.8146-3.9781-8.3945-4.0439z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m47.327 24.743c1.063-0.8475 4.8694 0.3945 5.5781 3.5032 0.7086 3.1123-2.1114 5.5087-3.5032 5.5781 0.5808-2.9516 0.1059-6.2466-2.0749-9.0813z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m48.025 39.84c1.326 0.2996 2.7288 4.0512 0.7379 6.5461-1.9909 2.495-5.6219 1.7827-6.5461 0.7379 2.6703-1.3844 4.9498-3.81 5.8082-7.284z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m28.182 19.259c-0.4859-1.0045 1.2018-3.8173 3.8173-3.8173s4.0183 2.6922 3.8173 3.8173c-2.2538-1.0045-4.9716-1.2237-7.6383 0z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m40.376 21.455c0.4822-1.0046 3.737-1.4429 5.3662 0.6064s0.3982 4.8182-0.6064 5.3662c-0.621-2.3854-2.1406-4.6502-4.7634-5.9726z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m46.264 32.355c1.085-0.2484 3.4557 2.0201 2.8749 4.5735s-3.5178 3.3169-4.5735 2.8749c1.4795-1.9726 2.3014-4.5735 1.6986-7.4484z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m41.413 43.756c0.8731 0.694 0.5735 3.9634-1.7826 5.0995-2.3562 1.1361-4.7854-0.6831-5.0996-1.7827 2.4658-0.073 5.0119-1.052 6.8822-3.3132z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m29.472 47.069c0 1.1141-2.7398 2.9187-5.0996 1.7826-2.3598-1.136-2.4511-4.168-1.7826-5.0995 1.5963 1.8813 3.9488 3.2621 6.8822 3.3132z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m19.437 39.804c-0.8694 0.6941-3.9927-0.3214-4.5735-2.8749-0.5809-2.5534 1.7315-4.515 2.8748-4.5735-0.4749 2.4219-0.0876 5.1215 1.6987 7.4484z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m18.864 27.428c-1.085-0.2484-2.2357-3.3206-0.6064-5.3662 1.6328-2.0457 4.61-1.4612 5.3662-0.6064-2.1882 1.1361-4.0585 3.1233-4.7635 5.9726z" clip-rule="evenodd" fill="#436544" fill-rule="evenodd"/>
<path d="m32.003 39.665c0.4499 0 0.8146-0.3647 0.8146-0.8146s-0.3647-0.8147-0.8146-0.8147-0.8146 0.3648-0.8146 0.8147 0.3647 0.8146 0.8146 0.8146z" fill="#436544"/>
<path d="m27.44 37.47c0.4499 0 0.8147-0.3648 0.8147-0.8147s-0.3648-0.8146-0.8147-0.8146-0.8146 0.3647-0.8146 0.8146 0.3647 0.8147 0.8146 0.8147z" fill="#436544"/>
<path d="m26.315 32.534c0.4499 0 0.8147-0.3647 0.8147-0.8146s-0.3648-0.8146-0.8147-0.8146-0.8146 0.3647-0.8146 0.8146 0.3647 0.8146 0.8146 0.8146z" fill="#436544"/>
<path d="m29.471 28.578c0.4499 0 0.8147-0.3647 0.8147-0.8146s-0.3648-0.8146-0.8147-0.8146c-0.4498 0-0.8146 0.3647-0.8146 0.8146s0.3648 0.8146 0.8146 0.8146z" fill="#436544"/>
<path d="m34.531 28.578c0.4499 0 0.8146-0.3647 0.8146-0.8146s-0.3647-0.8146-0.8146-0.8146-0.8146 0.3647-0.8146 0.8146 0.3647 0.8146 0.8146 0.8146z" fill="#436544"/>
<path d="m37.687 32.534c0.4499 0 0.8146-0.3647 0.8146-0.8146s-0.3647-0.8146-0.8146-0.8146-0.8146 0.3647-0.8146 0.8146 0.3647 0.8146 0.8146 0.8146z" fill="#436544"/>
<path d="m36.562 37.47c0.4499 0 0.8146-0.3648 0.8146-0.8147s-0.3647-0.8146-0.8146-0.8146-0.8146 0.3647-0.8146 0.8146 0.3647 0.8147 0.8146 0.8147z" fill="#436544"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 735 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M181.66,170.34a8,8,0,0,1,0,11.32l-48,48a8,8,0,0,1-11.32,0l-48-48a8,8,0,0,1,11.32-11.32L128,212.69l42.34-42.35A8,8,0,0,1,181.66,170.34Zm-96-84.68L128,43.31l42.34,42.35a8,8,0,0,0,11.32-11.32l-48-48a8,8,0,0,0-11.32,0l-48,48A8,8,0,0,0,85.66,85.66Z"></path></svg>

After

Width:  |  Height:  |  Size: 367 B

View File

@@ -204,7 +204,7 @@ Window {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
width: MySettings.fontSize === "Small" ? 86 : 100
width: 16 + 52 * theme.fontScale
color: theme.viewBarBackground
ColumnLayout {
@@ -213,20 +213,20 @@ Window {
anchors.topMargin: 30
anchors.horizontalCenter: parent.horizontalCenter
Layout.margins: 0
spacing: 18
spacing: 16
MyToolButton {
id: homeButton
backgroundColor: toggled ? theme.iconBackgroundViewBarHovered : theme.iconBackgroundViewBar
backgroundColorHovered: theme.iconBackgroundViewBarHovered
Layout.preferredWidth: 48
Layout.preferredHeight: 48
Layout.preferredWidth: 38 * theme.fontScale
Layout.preferredHeight: 38 * theme.fontScale
Layout.alignment: Qt.AlignCenter
toggledWidth: 0
toggled: homeView.isShown()
toggledColor: theme.iconBackgroundViewBarToggled
imageWidth: 34
imageHeight: 34
imageWidth: 25 * theme.fontScale
imageHeight: 25 * theme.fontScale
source: "qrc:/gpt4all/icons/home.svg"
Accessible.name: qsTr("Home view")
Accessible.description: qsTr("Home view of application")
@@ -238,10 +238,10 @@ Window {
Text {
Layout.topMargin: -20
text: qsTr("Home")
font.pixelSize: theme.fontSizeLargeCapped
font.pixelSize: theme.fontSizeMedium
font.bold: true
color: homeButton.hovered ? homeButton.backgroundColorHovered : homeButton.backgroundColor
Layout.preferredWidth: 48
Layout.preferredWidth: 38 * theme.fontScale
horizontalAlignment: Text.AlignHCenter
TapHandler {
onTapped: function(eventPoint, button) {
@@ -254,14 +254,14 @@ Window {
id: chatButton
backgroundColor: toggled ? theme.iconBackgroundViewBarHovered : theme.iconBackgroundViewBar
backgroundColorHovered: theme.iconBackgroundViewBarHovered
Layout.preferredWidth: 48
Layout.preferredHeight: 48
Layout.preferredWidth: 38 * theme.fontScale
Layout.preferredHeight: 38 * theme.fontScale
Layout.alignment: Qt.AlignCenter
toggledWidth: 0
toggled: chatView.isShown()
toggledColor: theme.iconBackgroundViewBarToggled
imageWidth: 34
imageHeight: 34
imageWidth: 25 * theme.fontScale
imageHeight: 25 * theme.fontScale
source: "qrc:/gpt4all/icons/chat.svg"
Accessible.name: qsTr("Chat view")
Accessible.description: qsTr("Chat view to interact with models")
@@ -273,10 +273,10 @@ Window {
Text {
Layout.topMargin: -20
text: qsTr("Chats")
font.pixelSize: theme.fontSizeLargeCapped
font.pixelSize: theme.fontSizeMedium
font.bold: true
color: chatButton.hovered ? chatButton.backgroundColorHovered : chatButton.backgroundColor
Layout.preferredWidth: 48
Layout.preferredWidth: 38 * theme.fontScale
horizontalAlignment: Text.AlignHCenter
TapHandler {
onTapped: function(eventPoint, button) {
@@ -289,13 +289,13 @@ Window {
id: modelsButton
backgroundColor: toggled ? theme.iconBackgroundViewBarHovered : theme.iconBackgroundViewBar
backgroundColorHovered: theme.iconBackgroundViewBarHovered
Layout.preferredWidth: 48
Layout.preferredHeight: 48
Layout.preferredWidth: 38 * theme.fontScale
Layout.preferredHeight: 38 * theme.fontScale
toggledWidth: 0
toggled: modelsView.isShown()
toggledColor: theme.iconBackgroundViewBarToggled
imageWidth: 34
imageHeight: 34
imageWidth: 25 * theme.fontScale
imageHeight: 25 * theme.fontScale
source: "qrc:/gpt4all/icons/models.svg"
Accessible.name: qsTr("Models")
Accessible.description: qsTr("Models view for installed models")
@@ -307,10 +307,10 @@ Window {
Text {
Layout.topMargin: -20
text: qsTr("Models")
font.pixelSize: theme.fontSizeLargeCapped
font.pixelSize: theme.fontSizeMedium
font.bold: true
color: modelsButton.hovered ? modelsButton.backgroundColorHovered : modelsButton.backgroundColor
Layout.preferredWidth: 48
Layout.preferredWidth: 38 * theme.fontScale
horizontalAlignment: Text.AlignHCenter
TapHandler {
onTapped: function(eventPoint, button) {
@@ -323,13 +323,13 @@ Window {
id: localdocsButton
backgroundColor: toggled ? theme.iconBackgroundViewBarHovered : theme.iconBackgroundViewBar
backgroundColorHovered: theme.iconBackgroundViewBarHovered
Layout.preferredWidth: 48
Layout.preferredHeight: 48
Layout.preferredWidth: 38 * theme.fontScale
Layout.preferredHeight: 38 * theme.fontScale
toggledWidth: 0
toggledColor: theme.iconBackgroundViewBarToggled
toggled: localDocsView.isShown()
imageWidth: 34
imageHeight: 34
imageWidth: 25 * theme.fontScale
imageHeight: 25 * theme.fontScale
source: "qrc:/gpt4all/icons/db.svg"
Accessible.name: qsTr("LocalDocs")
Accessible.description: qsTr("LocalDocs view to configure and use local docs")
@@ -341,10 +341,10 @@ Window {
Text {
Layout.topMargin: -20
text: qsTr("LocalDocs")
font.pixelSize: theme.fontSizeLargeCapped
font.pixelSize: theme.fontSizeMedium
font.bold: true
color: localdocsButton.hovered ? localdocsButton.backgroundColorHovered : localdocsButton.backgroundColor
Layout.preferredWidth: 48
Layout.preferredWidth: 38 * theme.fontScale
horizontalAlignment: Text.AlignHCenter
TapHandler {
onTapped: function(eventPoint, button) {
@@ -357,13 +357,13 @@ Window {
id: settingsButton
backgroundColor: toggled ? theme.iconBackgroundViewBarHovered : theme.iconBackgroundViewBar
backgroundColorHovered: theme.iconBackgroundViewBarHovered
Layout.preferredWidth: 48
Layout.preferredHeight: 48
Layout.preferredWidth: 38 * theme.fontScale
Layout.preferredHeight: 38 * theme.fontScale
toggledWidth: 0
toggledColor: theme.iconBackgroundViewBarToggled
toggled: settingsView.isShown()
imageWidth: 34
imageHeight: 34
imageWidth: 25 * theme.fontScale
imageHeight: 25 * theme.fontScale
source: "qrc:/gpt4all/icons/settings.svg"
Accessible.name: qsTr("Settings")
Accessible.description: qsTr("Settings view for application configuration")
@@ -375,10 +375,10 @@ Window {
Text {
Layout.topMargin: -20
text: qsTr("Settings")
font.pixelSize: theme.fontSizeLargeCapped
font.pixelSize: theme.fontSizeMedium
font.bold: true
color: settingsButton.hovered ? settingsButton.backgroundColorHovered : settingsButton.backgroundColor
Layout.preferredWidth: 48
Layout.preferredWidth: 38 * theme.fontScale
horizontalAlignment: Text.AlignHCenter
TapHandler {
onTapped: function(eventPoint, button) {
@@ -413,7 +413,7 @@ Window {
ColorOverlay {
id: antennaColored
visible: ModelList.installedModels.count !== 0 && (currentChat.isServer || currentChat.modelInfo.isOnline || MySettings.networkIsActive)
visible: ModelList.selectableModels.count !== 0 && (currentChat.isServer || currentChat.modelInfo.isOnline || MySettings.networkIsActive)
anchors.fill: antennaImage
source: antennaImage
color: theme.styledTextColor
@@ -498,7 +498,7 @@ Window {
Image {
id: image
anchors.centerIn: parent
sourceSize: Qt.size(60, 40)
sourceSize: Qt.size(48 * theme.fontScale, 32 * theme.fontScale)
fillMode: Image.PreserveAspectFit
mipmap: true
visible: false

View File

@@ -312,6 +312,7 @@
"filename": "all-MiniLM-L6-v2.gguf2.f16.gguf",
"filesize": "45949216",
"requires": "2.7.4",
"removedIn": "3.0.0",
"ramrequired": "1",
"parameters": "40 million",
"quant": "f16",

View File

@@ -367,8 +367,9 @@ QVariantMap ModelInfo::getFields() const
};
}
InstalledModels::InstalledModels(QObject *parent)
InstalledModels::InstalledModels(QObject *parent, bool selectable)
: QSortFilterProxyModel(parent)
, m_selectable(selectable)
{
connect(this, &InstalledModels::rowsInserted, this, &InstalledModels::countChanged);
connect(this, &InstalledModels::rowsRemoved, this, &InstalledModels::countChanged);
@@ -379,11 +380,15 @@ InstalledModels::InstalledModels(QObject *parent)
bool InstalledModels::filterAcceptsRow(int sourceRow,
const QModelIndex &sourceParent) const
{
/* TODO(jared): We should list incomplete models alongside installed models on the
* Models page. Simply replacing isDownloading with isIncomplete here doesn't work for
* some reason - the models show up as something else. */
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
bool isInstalled = sourceModel()->data(index, ModelList::InstalledRole).toBool();
bool isDownloading = sourceModel()->data(index, ModelList::DownloadingRole).toBool();
bool isEmbeddingModel = sourceModel()->data(index, ModelList::IsEmbeddingModelRole).toBool();
// list installed chat models
return isInstalled && !isEmbeddingModel;
return (isInstalled || (!m_selectable && isDownloading)) && !isEmbeddingModel;
}
DownloadableModels::DownloadableModels(QObject *parent)
@@ -403,11 +408,9 @@ bool DownloadableModels::filterAcceptsRow(int sourceRow,
// FIXME We can eliminate the 'expanded' code as the UI no longer uses this
bool withinLimit = sourceRow < (m_expanded ? sourceModel()->rowCount() : m_limit);
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
bool isDownloadable = !sourceModel()->data(index, ModelList::DescriptionRole).toString().isEmpty();
bool isInstalled = sourceModel()->data(index, ModelList::InstalledRole).toBool();
bool isIncomplete = sourceModel()->data(index, ModelList::IncompleteRole).toBool();
bool hasDescription = !sourceModel()->data(index, ModelList::DescriptionRole).toString().isEmpty();
bool isClone = sourceModel()->data(index, ModelList::IsCloneRole).toBool();
return withinLimit && !isClone && !isInstalled && (isDownloadable || isIncomplete);
return withinLimit && hasDescription && !isClone;
}
int DownloadableModels::count() const
@@ -446,6 +449,7 @@ ModelList *ModelList::globalInstance()
ModelList::ModelList()
: QAbstractListModel(nullptr)
, m_installedModels(new InstalledModels(this))
, m_selectableModels(new InstalledModels(this, /*selectable*/ true))
, m_downloadableModels(new DownloadableModels(this))
, m_asyncModelRequestOngoing(false)
, m_discoverLimit(20)
@@ -456,6 +460,7 @@ ModelList::ModelList()
, m_discoverInProgress(false)
{
m_installedModels->setSourceModel(this);
m_selectableModels->setSourceModel(this);
m_downloadableModels->setSourceModel(this);
connect(MySettings::globalInstance(), &MySettings::modelPathChanged, this, &ModelList::updateModelsFromDirectory);

View File

@@ -208,7 +208,7 @@ class InstalledModels : public QSortFilterProxyModel
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
explicit InstalledModels(QObject *parent);
explicit InstalledModels(QObject *parent, bool selectable = false);
int count() const { return rowCount(); }
Q_SIGNALS:
@@ -216,6 +216,9 @@ Q_SIGNALS:
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
private:
bool m_selectable;
};
class DownloadableModels : public QSortFilterProxyModel
@@ -252,6 +255,7 @@ class ModelList : public QAbstractListModel
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(InstalledModels* installedModels READ installedModels NOTIFY installedModelsChanged)
Q_PROPERTY(InstalledModels* selectableModels READ selectableModels NOTIFY selectableModelsChanged)
Q_PROPERTY(DownloadableModels* downloadableModels READ downloadableModels NOTIFY downloadableModelsChanged)
Q_PROPERTY(QList<QString> userDefaultModelList READ userDefaultModelList NOTIFY userDefaultModelListChanged)
Q_PROPERTY(bool asyncModelRequestOngoing READ asyncModelRequestOngoing NOTIFY asyncModelRequestOngoingChanged)
@@ -396,6 +400,7 @@ public:
const QList<QString> userDefaultModelList() const;
InstalledModels *installedModels() const { return m_installedModels; }
InstalledModels *selectableModels() const { return m_selectableModels; }
DownloadableModels *downloadableModels() const { return m_downloadableModels; }
static inline QString toFileSize(quint64 sz) {
@@ -433,6 +438,7 @@ public:
Q_SIGNALS:
void countChanged();
void installedModelsChanged();
void selectableModelsChanged();
void downloadableModelsChanged();
void userDefaultModelListChanged();
void asyncModelRequestOngoingChanged();
@@ -471,6 +477,7 @@ private:
mutable QMutex m_mutex;
QNetworkAccessManager m_networkManager;
InstalledModels *m_installedModels;
InstalledModels *m_selectableModels;
DownloadableModels *m_downloadableModels;
QList<ModelInfo*> m_models;
QHash<QString, ModelInfo*> m_modelMap;

View File

@@ -47,6 +47,7 @@ static const QVariantMap basicDefaults {
{ "localdocs/fileExtensions", QStringList { "txt", "pdf", "md", "rst" } },
{ "localdocs/useRemoteEmbed", false },
{ "localdocs/nomicAPIKey", "" },
{ "localdocs/embedDevice", "Auto" },
{ "network/attribution", "" },
};
@@ -77,6 +78,22 @@ static QString defaultLocalModelsPath()
return canonicalLocalPath;
}
static QStringList getDevices(bool skipKompute = false)
{
QStringList deviceList { "Auto" };
#if defined(Q_OS_MAC) && defined(__aarch64__)
deviceList << "Metal";
#else
std::vector<LLModel::GPUDevice> devices = LLModel::Implementation::availableGPUDevices();
for (LLModel::GPUDevice &d : devices) {
if (!skipKompute || strcmp(d.backend, "kompute"))
deviceList << QString::fromStdString(d.selectionName());
}
#endif
deviceList << "CPU";
return deviceList;
}
class MyPrivateSettings: public MySettings { };
Q_GLOBAL_STATIC(MyPrivateSettings, settingsInstance)
MySettings *MySettings::globalInstance()
@@ -85,18 +102,10 @@ MySettings *MySettings::globalInstance()
}
MySettings::MySettings()
: QObject{nullptr}
: QObject(nullptr)
, m_deviceList(getDevices())
, m_embeddingsDeviceList(getDevices(/*skipKompute*/ true))
{
QVector<QString> deviceList{ "Auto" };
#if defined(Q_OS_MAC) && defined(__aarch64__)
deviceList << "Metal";
#else
std::vector<LLModel::GPUDevice> devices = LLModel::Implementation::availableGPUDevices();
for (LLModel::GPUDevice &d : devices)
deviceList << QString::fromStdString(d.selectionName());
#endif
deviceList << "CPU";
setDeviceList(deviceList);
}
QVariant MySettings::getBasicSetting(const QString &name) const
@@ -113,17 +122,6 @@ void MySettings::setBasicSetting(const QString &name, const QVariant &value, std
QMetaObject::invokeMethod(this, u"%1Changed"_s.arg(signal.value_or(name)).toLatin1().constData());
}
Q_INVOKABLE QVector<QString> MySettings::deviceList() const
{
return m_deviceList;
}
void MySettings::setDeviceList(const QVector<QString> &value)
{
m_deviceList = value;
emit deviceListChanged();
}
void MySettings::restoreModelDefaults(const ModelInfo &info)
{
setModelTemperature(info, info.m_temperature);
@@ -162,6 +160,7 @@ void MySettings::restoreLocalDocsDefaults()
setLocalDocsFileExtensions(basicDefaults.value("localdocs/fileExtensions").toStringList());
setLocalDocsUseRemoteEmbed(basicDefaults.value("localdocs/useRemoteEmbed").toBool());
setLocalDocsNomicAPIKey(basicDefaults.value("localdocs/nomicAPIKey").toString());
setLocalDocsEmbedDevice(basicDefaults.value("localdocs/embedDevice").toString());
}
void MySettings::eraseModel(const ModelInfo &info)
@@ -382,6 +381,7 @@ bool MySettings::localDocsShowReferences() const { return getBasicSetting
QStringList MySettings::localDocsFileExtensions() const { return getBasicSetting("localdocs/fileExtensions").toStringList(); }
bool MySettings::localDocsUseRemoteEmbed() const { return getBasicSetting("localdocs/useRemoteEmbed").toBool(); }
QString MySettings::localDocsNomicAPIKey() const { return getBasicSetting("localdocs/nomicAPIKey" ).toString(); }
QString MySettings::localDocsEmbedDevice() const { return getBasicSetting("localdocs/embedDevice" ).toString(); }
QString MySettings::networkAttribution() const { return getBasicSetting("network/attribution" ).toString(); }
void MySettings::setSaveChatsContext(bool value) { setBasicSetting("saveChatsContext", value); }
@@ -397,6 +397,7 @@ void MySettings::setLocalDocsShowReferences(bool value) { setBasic
void MySettings::setLocalDocsFileExtensions(const QStringList &value) { setBasicSetting("localdocs/fileExtensions", value, "localDocsFileExtensions"); }
void MySettings::setLocalDocsUseRemoteEmbed(bool value) { setBasicSetting("localdocs/useRemoteEmbed", value, "localDocsUseRemoteEmbed"); }
void MySettings::setLocalDocsNomicAPIKey(const QString &value) { setBasicSetting("localdocs/nomicAPIKey", value, "localDocsNomicAPIKey"); }
void MySettings::setLocalDocsEmbedDevice(const QString &value) { setBasicSetting("localdocs/embedDevice", value, "localDocsEmbedDevice"); }
void MySettings::setNetworkAttribution(const QString &value) { setBasicSetting("network/attribution", value, "networkAttribution"); }
QString MySettings::modelPath()
@@ -446,11 +447,10 @@ QString MySettings::device()
void MySettings::setDevice(const QString &value)
{
if (device() == value)
return;
m_settings.setValue("device", value);
emit deviceChanged();
if (device() != value) {
m_settings.setValue("device", value);
emit deviceChanged();
}
}
bool MySettings::forceMetal() const
@@ -460,10 +460,10 @@ bool MySettings::forceMetal() const
void MySettings::setForceMetal(bool value)
{
if (m_forceMetal == value)
return;
m_forceMetal = value;
emit forceMetalChanged(value);
if (m_forceMetal != value) {
m_forceMetal = value;
emit forceMetalChanged(value);
}
}
bool MySettings::networkIsActive() const

View File

@@ -31,11 +31,13 @@ class MySettings : public QObject
Q_PROPERTY(QStringList localDocsFileExtensions READ localDocsFileExtensions WRITE setLocalDocsFileExtensions NOTIFY localDocsFileExtensionsChanged)
Q_PROPERTY(bool localDocsUseRemoteEmbed READ localDocsUseRemoteEmbed WRITE setLocalDocsUseRemoteEmbed NOTIFY localDocsUseRemoteEmbedChanged)
Q_PROPERTY(QString localDocsNomicAPIKey READ localDocsNomicAPIKey WRITE setLocalDocsNomicAPIKey NOTIFY localDocsNomicAPIKeyChanged)
Q_PROPERTY(QString localDocsEmbedDevice READ localDocsEmbedDevice WRITE setLocalDocsEmbedDevice NOTIFY localDocsEmbedDeviceChanged)
Q_PROPERTY(QString networkAttribution READ networkAttribution WRITE setNetworkAttribution NOTIFY networkAttributionChanged)
Q_PROPERTY(bool networkIsActive READ networkIsActive WRITE setNetworkIsActive NOTIFY networkIsActiveChanged)
Q_PROPERTY(bool networkUsageStatsActive READ networkUsageStatsActive WRITE setNetworkUsageStatsActive NOTIFY networkUsageStatsActiveChanged)
Q_PROPERTY(QString device READ device WRITE setDevice NOTIFY deviceChanged)
Q_PROPERTY(QVector<QString> deviceList READ deviceList NOTIFY deviceListChanged)
Q_PROPERTY(QStringList deviceList MEMBER m_deviceList CONSTANT)
Q_PROPERTY(QStringList embeddingsDeviceList MEMBER m_embeddingsDeviceList CONSTANT)
Q_PROPERTY(int networkPort READ networkPort WRITE setNetworkPort NOTIFY networkPortChanged)
public:
@@ -138,6 +140,8 @@ public:
void setLocalDocsUseRemoteEmbed(bool value);
QString localDocsNomicAPIKey() const;
void setLocalDocsNomicAPIKey(const QString &value);
QString localDocsEmbedDevice() const;
void setLocalDocsEmbedDevice(const QString &value);
// Network settings
QString networkAttribution() const;
@@ -151,9 +155,6 @@ public:
int networkPort() const;
void setNetworkPort(int value);
QVector<QString> deviceList() const;
void setDeviceList(const QVector<QString> &value);
Q_SIGNALS:
void nameChanged(const ModelInfo &info);
void filenameChanged(const ModelInfo &info);
@@ -185,18 +186,19 @@ Q_SIGNALS:
void localDocsFileExtensionsChanged();
void localDocsUseRemoteEmbedChanged();
void localDocsNomicAPIKeyChanged();
void localDocsEmbedDeviceChanged();
void networkAttributionChanged();
void networkIsActiveChanged();
void networkPortChanged();
void networkUsageStatsActiveChanged();
void attemptModelLoadChanged();
void deviceChanged();
void deviceListChanged();
private:
QSettings m_settings;
bool m_forceMetal;
QVector<QString> m_deviceList;
const QStringList m_deviceList;
const QStringList m_embeddingsDeviceList;
private:
explicit MySettings();

View File

@@ -32,7 +32,7 @@ Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.margins: 30
spacing: 50
spacing: 20
RowLayout {
Layout.fillWidth: true
@@ -61,10 +61,30 @@ Rectangle {
}
}
ColumnLayout {
Text {
id: addDocBanner
Layout.alignment: Qt.AlignBottom | Qt.AlignHCenter
horizontalAlignment: Qt.AlignHCenter
text: qsTr("Add Document Collection")
font.pixelSize: theme.fontSizeBanner
color: theme.titleTextColor
}
Text {
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
Layout.maximumWidth: addDocBanner.width
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignJustify
text: qsTr("Add a folder containing plain text files, PDFs, or Markdown. Configure additional extensions in Settings.")
font.pixelSize: theme.fontSizeLarger
color: theme.titleInfoTextColor
}
GridLayout {
id: root
Layout.alignment: Qt.AlignTop | Qt.AlignCenter
spacing: 50
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
rowSpacing: 50
columnSpacing: 20
property alias collection: collection.text
property alias folder_path: folderEdit.text
@@ -80,17 +100,21 @@ Rectangle {
folderDialog.open();
}
Text {
horizontalAlignment: Qt.AlignHCenter
text: qsTr("New Local Doc\nCollection")
font.pixelSize: theme.fontSizeBanner
color: theme.titleTextColor
Label {
Layout.row: 2
Layout.column: 0
text: qsTr("Name")
font.bold: true
font.pixelSize: theme.fontSizeLarger
color: theme.settingsTitleTextColor
}
MyTextField {
id: collection
Layout.alignment: Qt.AlignCenter
Layout.row: 2
Layout.column: 1
Layout.minimumWidth: 400
Layout.alignment: Qt.AlignRight
horizontalAlignment: Text.AlignJustify
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
@@ -109,10 +133,21 @@ Rectangle {
}
}
Label {
Layout.row: 3
Layout.column: 0
text: qsTr("Folder")
font.bold: true
font.pixelSize: theme.fontSizeLarger
color: theme.settingsTitleTextColor
}
RowLayout {
Layout.alignment: Qt.AlignCenter
Layout.row: 3
Layout.column: 1
Layout.minimumWidth: 400
Layout.maximumWidth: 400
Layout.alignment: Qt.AlignRight
spacing: 10
MyDirectoryField {
id: folderEdit
@@ -143,8 +178,9 @@ Rectangle {
}
MyButton {
Layout.alignment: Qt.AlignCenter
Layout.minimumWidth: 400
Layout.row: 4
Layout.column: 1
Layout.alignment: Qt.AlignRight
text: qsTr("Create Collection")
onClicked: {
var isError = false;

View File

@@ -365,7 +365,12 @@ Rectangle {
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Description")
Accessible.description: qsTr("File description")
onLinkActivated: Qt.openUrlExternally(link)
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
@@ -418,7 +423,7 @@ Rectangle {
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
visible: installed || downloadError !== ""
visible: !isDownloading && (installed || isIncomplete)
Accessible.description: qsTr("Remove model from filesystem")
onClicked: {
Download.removeModel(filename);
@@ -603,13 +608,13 @@ Rectangle {
Layout.rightMargin: 20
Text {
text: qsTr("File size")
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
color: theme.mutedDarkTextColor
}
Text {
text: filesize
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
font.bold: true
}
}
@@ -625,13 +630,13 @@ Rectangle {
Layout.rightMargin: 20
Text {
text: qsTr("RAM required")
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
color: theme.mutedDarkTextColor
}
Text {
text: ramrequired >= 0 ? ramrequired + qsTr(" GB") : "?"
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
font.bold: true
}
}
@@ -647,13 +652,13 @@ Rectangle {
Layout.rightMargin: 20
Text {
text: qsTr("Parameters")
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
color: theme.mutedDarkTextColor
}
Text {
text: parameters !== "" ? parameters : "?"
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
font.bold: true
}
}
@@ -669,13 +674,13 @@ Rectangle {
Layout.rightMargin: 20
Text {
text: qsTr("Quant")
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
color: theme.mutedDarkTextColor
}
Text {
text: quant
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
font.bold: true
}
}
@@ -691,13 +696,13 @@ Rectangle {
Layout.rightMargin: 20
Text {
text: qsTr("Type")
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
color: theme.mutedDarkTextColor
}
Text {
text: type
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
font.bold: true
}
}

View File

@@ -62,9 +62,19 @@ MySettingsTab {
rowSpacing: 30
columnSpacing: 10
ColumnLayout {
Label {
Layout.row: 0
Layout.column: 0
Layout.bottomMargin: 10
color: theme.settingsTitleTextColor
font.pixelSize: theme.fontSizeBannerSmall
font.bold: true
text: qsTr("Application Settings")
}
ColumnLayout {
Layout.row: 1
Layout.column: 0
Layout.columnSpan: 3
Layout.fillWidth: true
spacing: 10
@@ -72,7 +82,7 @@ MySettingsTab {
color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: "General"
text: qsTr("General")
}
Rectangle {
@@ -85,22 +95,21 @@ MySettingsTab {
MySettingsLabel {
id: themeLabel
text: qsTr("Theme")
helpText: qsTr("Customize the colors of GPT4All")
Layout.row: 1
helpText: qsTr("The application color scheme.")
Layout.row: 2
Layout.column: 0
}
MyComboBox {
id: themeBox
Layout.row: 1
Layout.row: 2
Layout.column: 2
Layout.minimumWidth: 200
Layout.maximumWidth: 200
Layout.fillWidth: false
Layout.alignment: Qt.AlignRight
model: [qsTr("Dark"), qsTr("Light"), qsTr("LegacyDark")]
Accessible.role: Accessible.ComboBox
Accessible.name: qsTr("Color theme")
Accessible.description: qsTr("Color theme for the chat client to use")
Accessible.name: themeLabel.text
Accessible.description: themeLabel.helpText
function updateModel() {
themeBox.currentIndex = themeBox.indexOfValue(MySettings.chatTheme);
}
@@ -120,22 +129,21 @@ MySettingsTab {
MySettingsLabel {
id: fontLabel
text: qsTr("Font Size")
helpText: qsTr("How big your font is displayed")
Layout.row: 2
helpText: qsTr("The size of text in the application.")
Layout.row: 3
Layout.column: 0
}
MyComboBox {
id: fontBox
Layout.row: 2
Layout.row: 3
Layout.column: 2
Layout.minimumWidth: 200
Layout.maximumWidth: 200
Layout.fillWidth: false
Layout.alignment: Qt.AlignRight
model: ["Small", "Medium", "Large"]
Accessible.role: Accessible.ComboBox
Accessible.name: qsTr("Font size")
Accessible.description: qsTr("Font size of the chat client")
Accessible.name: fontLabel.text
Accessible.description: fontLabel.helpText
function updateModel() {
fontBox.currentIndex = fontBox.indexOfValue(MySettings.fontSize);
}
@@ -155,59 +163,54 @@ MySettingsTab {
MySettingsLabel {
id: deviceLabel
text: qsTr("Device")
helpText: qsTr("The hardware device used to load the model")
Layout.row: 3
helpText: qsTr('The compute device used for text generation. "Auto" uses Vulkan or Metal.')
Layout.row: 4
Layout.column: 0
}
MyComboBox {
id: deviceBox
Layout.row: 3
Layout.row: 4
Layout.column: 2
Layout.minimumWidth: 400
Layout.maximumWidth: 400
Layout.fillWidth: false
Layout.alignment: Qt.AlignRight
model: MySettings.deviceList
Accessible.role: Accessible.ComboBox
Accessible.name: qsTr("Device")
Accessible.description: qsTr("Device of the chat client")
Accessible.name: deviceLabel.text
Accessible.description: deviceLabel.helpText
function updateModel() {
deviceBox.currentIndex = deviceBox.indexOfValue(MySettings.device);
}
Component.onCompleted: {
deviceBox.updateModel()
deviceBox.updateModel();
}
Connections {
target: MySettings
function onDeviceChanged() {
deviceBox.updateModel()
}
function onDeviceListChanged() {
deviceBox.updateModel()
deviceBox.updateModel();
}
}
onActivated: {
MySettings.device = deviceBox.currentText
MySettings.device = deviceBox.currentText;
}
}
MySettingsLabel {
id: defaultModelLabel
text: qsTr("Default model")
helpText: qsTr("The preferred default model")
Layout.row: 4
text: qsTr("Default Model")
helpText: qsTr("The preferred model for new chats. Also used as the local server fallback.")
Layout.row: 5
Layout.column: 0
}
MyComboBox {
id: comboBox
Layout.row: 4
Layout.row: 5
Layout.column: 2
Layout.minimumWidth: 400
Layout.maximumWidth: 400
Layout.alignment: Qt.AlignRight
model: ModelList.userDefaultModelList
Accessible.role: Accessible.ComboBox
Accessible.name: qsTr("Default model")
Accessible.description: qsTr("Default model to use; the first item is the current default model")
Accessible.name: defaultModelLabel.text
Accessible.description: defaultModelLabel.helpText
function updateModel() {
comboBox.currentIndex = comboBox.indexOfValue(MySettings.userDefaultModel);
}
@@ -226,14 +229,14 @@ MySettingsTab {
}
MySettingsLabel {
id: modelPathLabel
text: qsTr("Download path")
helpText: qsTr("The download folder for models")
Layout.row: 5
text: qsTr("Download Path")
helpText: qsTr("Where to store local models and the LocalDocs database.")
Layout.row: 6
Layout.column: 0
}
RowLayout {
Layout.row: 5
Layout.row: 6
Layout.column: 2
Layout.alignment: Qt.AlignRight
Layout.minimumWidth: 400
@@ -245,11 +248,8 @@ MySettingsTab {
font.pixelSize: theme.fontSizeLarge
implicitWidth: 300
Layout.fillWidth: true
ToolTip.text: qsTr("Path where model files will be downloaded to")
ToolTip.visible: hovered
Accessible.role: Accessible.ToolTip
Accessible.name: modelPathDisplayField.text
Accessible.description: ToolTip.text
Accessible.name: modelPathLabel.text
Accessible.description: modelPathLabel.helpText
onEditingFinished: {
if (isValid) {
MySettings.modelPath = modelPathDisplayField.text
@@ -271,29 +271,32 @@ MySettingsTab {
MySettingsLabel {
id: dataLakeLabel
text: qsTr("Opensource Datalake")
helpText: qsTr("Send your data to the GPT4All Open Source Datalake.")
Layout.row: 6
text: qsTr("Enable Datalake")
helpText: qsTr("Send chats and feedback to the GPT4All Open-Source Datalake.")
Layout.row: 7
Layout.column: 0
}
MyCheckBox {
id: dataLakeBox
Layout.row: 6
Layout.row: 7
Layout.column: 2
Layout.alignment: Qt.AlignRight
checked: MySettings.networkIsActive
onClicked: {
if (MySettings.networkIsActive) {
MySettings.networkIsActive = false
} else
networkDialog.open()
Component.onCompleted: { dataLakeBox.checked = MySettings.networkIsActive; }
Connections {
target: MySettings
function onNetworkIsActiveChanged() { dataLakeBox.checked = MySettings.networkIsActive; }
}
onClicked: {
if (MySettings.networkIsActive)
MySettings.networkIsActive = false;
else
networkDialog.open();
dataLakeBox.checked = MySettings.networkIsActive;
}
ToolTip.text: qsTr("Reveals a dialogue where you can opt-in for sharing data over network")
ToolTip.visible: hovered
}
ColumnLayout {
Layout.row: 7
Layout.row: 8
Layout.column: 0
Layout.columnSpan: 3
Layout.fillWidth: true
@@ -302,7 +305,7 @@ MySettingsTab {
color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: "Advanced"
text: qsTr("Advanced")
}
Rectangle {
@@ -315,18 +318,16 @@ MySettingsTab {
MySettingsLabel {
id: nThreadsLabel
text: qsTr("CPU Threads")
helpText: qsTr("Number of CPU threads for inference and embedding")
Layout.row: 8
helpText: qsTr("The number of CPU threads used for inference and embedding.")
Layout.row: 9
Layout.column: 0
}
MyTextField {
text: MySettings.threadCount
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
ToolTip.text: qsTr("Amount of processing threads to use bounded by 1 and number of logical processors")
ToolTip.visible: hovered
Layout.alignment: Qt.AlignRight
Layout.row: 8
Layout.row: 9
Layout.column: 2
Layout.minimumWidth: 200
Layout.maximumWidth: 200
@@ -348,47 +349,43 @@ MySettingsTab {
}
MySettingsLabel {
id: saveChatsContextLabel
text: qsTr("Save chat context")
helpText: qsTr("Save chat context to disk")
Layout.row: 9
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: 10
Layout.column: 0
}
MyCheckBox {
id: saveChatsContextBox
Layout.row: 9
Layout.row: 10
Layout.column: 2
Layout.alignment: Qt.AlignRight
checked: MySettings.saveChatsContext
onClicked: {
MySettings.saveChatsContext = !MySettings.saveChatsContext
}
ToolTip.text: qsTr("WARNING: Saving chats to disk can be ~2GB per chat")
ToolTip.visible: hovered
}
MySettingsLabel {
id: serverChatLabel
text: qsTr("Enable API server")
helpText: qsTr("A local http server running on local port")
Layout.row: 10
text: qsTr("Enable Local Server")
helpText: qsTr("Expose an OpenAI-Compatible server to localhost. WARNING: Results in increased resource usage.")
Layout.row: 11
Layout.column: 0
}
MyCheckBox {
id: serverChatBox
Layout.row: 10
Layout.row: 11
Layout.column: 2
Layout.alignment: Qt.AlignRight
checked: MySettings.serverChat
onClicked: {
MySettings.serverChat = !MySettings.serverChat
}
ToolTip.text: qsTr("WARNING: This enables the gui to act as a local REST web server(OpenAI API compliant) for API requests and will increase your RAM usage as well")
ToolTip.visible: hovered
}
MySettingsLabel {
id: serverPortLabel
text: qsTr("API Server Port:")
helpText: qsTr("A local port to run the server (Requires restart")
Layout.row: 11
text: qsTr("API Server Port")
helpText: qsTr("The port to use for the local server. Requires restart.")
Layout.row: 12
Layout.column: 0
}
MyTextField {
@@ -396,9 +393,7 @@ MySettingsTab {
text: MySettings.networkPort
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
ToolTip.text: qsTr("Api server port. WARNING: You need to restart the application for it to take effect")
ToolTip.visible: hovered
Layout.row: 11
Layout.row: 12
Layout.column: 2
Layout.minimumWidth: 200
Layout.maximumWidth: 200
@@ -416,8 +411,8 @@ MySettingsTab {
}
}
Accessible.role: Accessible.EditableText
Accessible.name: serverPortField.text
Accessible.description: ToolTip.text
Accessible.name: serverPortLabel.text
Accessible.description: serverPortLabel.helpText
}
/*MySettingsLabel {
@@ -441,8 +436,8 @@ MySettingsTab {
MySettingsLabel {
id: updatesLabel
text: qsTr("Check for updates")
helpText: qsTr("Click to see if an update to the application is available");
text: qsTr("Check For Updates")
helpText: qsTr("Manually check for an update to GPT4All.");
Layout.row: 14
Layout.column: 0
}

View File

@@ -46,7 +46,7 @@ Rectangle {
font.pixelSize: theme.fontSizeLarger
topPadding: 24
bottomPadding: 24
text: qsTr("\uFF0B New chat")
text: qsTr("\uFF0B New Chat")
Accessible.description: qsTr("Create a new chat")
onClicked: {
ChatListModel.addChat()
@@ -110,7 +110,7 @@ Rectangle {
bottomPadding: 5
text: parent.section
color: theme.chatDrawerSectionHeader
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmallest
}
}
}

View File

@@ -205,13 +205,12 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 20
spacing: 0
Rectangle {
Layout.alignment: Qt.AlignLeft
Layout.leftMargin: 30
Layout.fillWidth: true
Layout.preferredWidth: 100
color: "transparent"
Layout.preferredHeight: childrenRect.height
MyToolButton {
@@ -237,13 +236,20 @@ Rectangle {
id: comboBox
Layout.alignment: Qt.AlignHCenter
Layout.fillHeight: true
Layout.preferredWidth: 350
Layout.maximumWidth: 675
Layout.preferredWidth: 550
Layout.leftMargin: {
// This function works in tandem with the preferredWidth and the layout to
// provide the maximum size combobox we can have at the smallest window width
// we allow with the largest font size we allow. It is unfortunately based
// upon a magic number that was produced through trial and error for something
// I don't fully understand.
return -Math.max(0, comboBox.width / 2 + collectionsButton.width + 110 /*magic*/ - comboLayout.width / 2);
}
enabled: !currentChat.isServer
&& !currentChat.trySwitchContextInProgress
&& !currentChat.isCurrentlyLoading
&& ModelList.installedModels.count !== 0
model: ModelList.installedModels
&& ModelList.selectableModels.count !== 0
model: ModelList.selectableModels
valueRole: "id"
textRole: "name"
@@ -266,8 +272,8 @@ Rectangle {
ProgressBar {
id: modelProgress
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.horizontalCenter: parent.horizontalCenter
width: contentRow.width + 20
visible: currentChat.isCurrentlyLoading
height: 10
value: currentChat.modelLoadingPercentage
@@ -286,38 +292,110 @@ Rectangle {
}
}
}
contentItem: Text {
anchors.horizontalCenter: parent.horizontalCenter
leftPadding: 10
rightPadding: {
if (ejectButton.visible && reloadButton)
return 105;
if (reloadButton.visible)
return 65
return 25
contentItem: Item {
RowLayout {
id: contentRow
anchors.centerIn: parent
spacing: 0
RowLayout {
id: miniButtonsRow
clip: true
Behavior on Layout.preferredWidth {
NumberAnimation {
duration: 300
easing.type: Easing.InOutQuad
}
}
Layout.preferredWidth: {
if (!comboBox.hovered)
return 0
return (reloadButton.visible ? reloadButton.width : 0) + (ejectButton.visible ? ejectButton.width : 0)
}
MyMiniButton {
id: reloadButton
Layout.alignment: Qt.AlignCenter
visible: currentChat.modelLoadingError === ""
&& !currentChat.trySwitchContextInProgress
&& !currentChat.isCurrentlyLoading
&& (currentChat.isModelLoaded || currentModelName() !== "")
source: "qrc:/gpt4all/icons/regenerate.svg"
backgroundColor: theme.textColor
backgroundColorHovered: theme.styledTextColor
onClicked: {
if (currentChat.isModelLoaded)
currentChat.forceReloadModel();
else
currentChat.reloadModel();
}
ToolTip.text: qsTr("Reload the currently loaded model")
ToolTip.visible: hovered
}
MyMiniButton {
id: ejectButton
Layout.alignment: Qt.AlignCenter
visible: currentChat.isModelLoaded && !currentChat.isCurrentlyLoading
source: "qrc:/gpt4all/icons/eject.svg"
backgroundColor: theme.textColor
backgroundColorHovered: theme.styledTextColor
onClicked: {
currentChat.forceUnloadModel();
}
ToolTip.text: qsTr("Eject the currently loaded model")
ToolTip.visible: hovered
}
}
Text {
id: comboBoxText
leftPadding: 10
rightPadding: 10
text: {
if (ModelList.selectableModels.count === 0)
return qsTr("No model installed...")
if (currentChat.modelLoadingError !== "")
return qsTr("Model loading error...")
if (currentChat.trySwitchContextInProgress === 1)
return qsTr("Waiting for model...")
if (currentChat.trySwitchContextInProgress === 2)
return qsTr("Switching context...")
if (currentModelName() === "")
return qsTr("Choose a model...")
if (currentChat.modelLoadingPercentage === 0.0)
return qsTr("Reload \u00B7 ") + currentModelName()
if (currentChat.isCurrentlyLoading)
return qsTr("Loading \u00B7 ") + currentModelName()
return currentModelName()
}
font.pixelSize: theme.fontSizeLarger
color: theme.iconBackgroundLight
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
Item {
Layout.minimumWidth: updown.width
Layout.minimumHeight: updown.height
Image {
id: updown
anchors.verticalCenter: parent.verticalCenter
sourceSize.width: comboBoxText.font.pixelSize
sourceSize.height: comboBoxText.font.pixelSize
mipmap: true
visible: false
source: "qrc:/gpt4all/icons/up_down.svg"
}
ColorOverlay {
anchors.fill: updown
source: updown
color: comboBoxText.color
}
}
}
text: {
if (ModelList.installedModels.count === 0)
return qsTr("No model installed...")
if (currentChat.modelLoadingError !== "")
return qsTr("Model loading error...")
if (currentChat.trySwitchContextInProgress === 1)
return qsTr("Waiting for model...")
if (currentChat.trySwitchContextInProgress === 2)
return qsTr("Switching context...")
if (currentModelName() === "")
return qsTr("Choose a model...")
if (currentChat.modelLoadingPercentage === 0.0)
return qsTr("Reload \u00B7 ") + currentModelName()
if (currentChat.isCurrentlyLoading)
return qsTr("Loading \u00B7 ") + currentModelName()
return currentModelName()
}
font.pixelSize: theme.fontSizeLarger
color: theme.iconBackgroundLight
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
delegate: ItemDelegate {
id: comboItemDelegate
@@ -330,12 +408,12 @@ Rectangle {
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
color: (index % 2 === 0 ? theme.darkContrast : theme.lightContrast)
border.width: highlighted
border.color: theme.accentColor
color: highlighted ? theme.lightContrast : theme.darkContrast
}
highlighted: comboBox.highlightedIndex === index
}
indicator: Item {
}
popup: Popup {
id: comboItemPopup
y: comboBox.height - 1
@@ -365,7 +443,6 @@ Rectangle {
}
}
Accessible.role: Accessible.ComboBox
Accessible.name: currentModelName()
Accessible.description: qsTr("The top item is the current model")
onActivated: function (index) {
@@ -379,46 +456,6 @@ Rectangle {
comboBox.changeModel(index);
}
}
MyMiniButton {
id: ejectButton
visible: currentChat.isModelLoaded && !currentChat.isCurrentlyLoading
z: 500
anchors.right: parent.right
anchors.rightMargin: 50
anchors.verticalCenter: parent.verticalCenter
source: "qrc:/gpt4all/icons/eject.svg"
backgroundColor: theme.mutedLightTextColor
backgroundColorHovered: theme.iconBackgroundLight
onClicked: {
currentChat.forceUnloadModel();
}
ToolTip.text: qsTr("Eject the currently loaded model")
ToolTip.visible: hovered
}
MyMiniButton {
id: reloadButton
visible: currentChat.modelLoadingError === ""
&& !currentChat.trySwitchContextInProgress
&& !currentChat.isCurrentlyLoading
&& (currentChat.isModelLoaded || currentModelName() !== "")
z: 500
anchors.right: ejectButton.visible ? ejectButton.left : parent.right
anchors.rightMargin: ejectButton.visible ? 10 : 50
anchors.verticalCenter: parent.verticalCenter
source: "qrc:/gpt4all/icons/regenerate.svg"
backgroundColor: theme.mutedLightTextColor
backgroundColorHovered: theme.iconBackgroundLight
onClicked: {
if (currentChat.isModelLoaded)
currentChat.forceReloadModel();
else
currentChat.reloadModel();
}
ToolTip.text: qsTr("Reload the currently loaded model")
ToolTip.visible: hovered
}
}
Rectangle {
@@ -426,94 +463,97 @@ Rectangle {
Layout.alignment: Qt.AlignRight
Layout.rightMargin: 30
Layout.fillWidth: true
Layout.preferredWidth: 100
Layout.preferredHeight: childrenRect.height
clip: true
RowLayout {
spacing: 20
MyButton {
id: collectionsButton
clip: true
anchors.right: parent.right
MyButton {
id: collectionsButton
borderWidth: 0
backgroundColor: theme.collectionsButtonBackground
backgroundColorHovered: theme.collectionsButtonBackgroundHovered
backgroundRadius: 5
padding: 15
topPadding: 8
bottomPadding: 8
borderWidth: 0
backgroundColor: theme.collectionsButtonBackground
backgroundColorHovered: theme.collectionsButtonBackgroundHovered
backgroundRadius: 5
padding: 15
topPadding: 8
bottomPadding: 8
contentItem: RowLayout {
spacing: 10
Item {
visible: currentChat.collectionModel.count === 0
Layout.minimumWidth: collectionsImage.width
Layout.minimumHeight: collectionsImage.height
Image {
id: collectionsImage
anchors.verticalCenter: parent.verticalCenter
sourceSize.width: 24
sourceSize.height: 24
mipmap: true
visible: false
source: "qrc:/gpt4all/icons/db.svg"
}
ColorOverlay {
anchors.fill: collectionsImage
source: collectionsImage
color: theme.collectionsButtonForeground
}
contentItem: RowLayout {
spacing: 10
Item {
visible: currentChat.collectionModel.count === 0
Layout.minimumWidth: collectionsImage.width
Layout.minimumHeight: collectionsImage.height
Image {
id: collectionsImage
anchors.verticalCenter: parent.verticalCenter
sourceSize.width: 24
sourceSize.height: 24
mipmap: true
visible: false
source: "qrc:/gpt4all/icons/db.svg"
}
MyBusyIndicator {
visible: currentChat.collectionModel.updatingCount !== 0
color: theme.collectionsButtonProgress
size: 24
Layout.minimumWidth: 24
Layout.minimumHeight: 24
Text {
anchors.centerIn: parent
text: currentChat.collectionModel.updatingCount
color: theme.collectionsButtonForeground
font.pixelSize: 14 // fixed regardless of theme
}
}
Rectangle {
visible: currentChat.collectionModel.count !== 0
radius: 6
ColorOverlay {
anchors.fill: collectionsImage
source: collectionsImage
color: theme.collectionsButtonForeground
Layout.minimumWidth: collectionsImage.width
Layout.minimumHeight: collectionsImage.height
Text {
anchors.centerIn: parent
text: currentChat.collectionModel.count
color: theme.collectionsButtonText
font.pixelSize: 14 // fixed regardless of theme
}
}
}
MyBusyIndicator {
visible: currentChat.collectionModel.updatingCount !== 0
color: theme.collectionsButtonProgress
size: 24
Layout.minimumWidth: 24
Layout.minimumHeight: 24
Text {
text: qsTr("LocalDocs")
anchors.centerIn: parent
text: currentChat.collectionModel.updatingCount
color: theme.collectionsButtonForeground
font.pixelSize: theme.fontSizeLarge
font.pixelSize: 14 // fixed regardless of theme
}
}
fontPixelSize: theme.fontSizeLarge
background: Rectangle {
radius: collectionsButton.backgroundRadius
color: collectionsButton.toggled ? collectionsButton.backgroundColorHovered : collectionsButton.backgroundColor
Rectangle {
visible: currentChat.collectionModel.count !== 0
radius: 6
color: theme.collectionsButtonForeground
Layout.minimumWidth: collectionsImage.width
Layout.minimumHeight: collectionsImage.height
Text {
anchors.centerIn: parent
text: currentChat.collectionModel.count
color: theme.collectionsButtonText
font.pixelSize: 14 // fixed regardless of theme
}
}
Accessible.name: qsTr("Add documents")
Accessible.description: qsTr("add collections of documents to the chat")
onClicked: {
conversation.toggleRightPanel()
Text {
text: qsTr("LocalDocs")
color: theme.collectionsButtonForeground
font.pixelSize: theme.fontSizeLarge
}
}
fontPixelSize: theme.fontSizeLarge
background: Rectangle {
radius: collectionsButton.backgroundRadius
// TODO(jared): either use collectionsButton-specific theming, or don't - this is inconsistent
color: conversation.state === "expanded" ? (
collectionsButton.hovered ? theme.lightButtonBackgroundHovered : theme.lightButtonBackground
) : (
collectionsButton.hovered ? theme.lighterButtonBackground : theme.lighterButtonBackgroundHovered
)
}
Accessible.name: qsTr("Add documents")
Accessible.description: qsTr("add collections of documents to the chat")
onClicked: {
conversation.toggleRightPanel()
}
}
}
}
@@ -598,10 +638,10 @@ Rectangle {
id: homePage
color: "transparent"
anchors.fill: parent
visible: !currentChat.isModelLoaded && (ModelList.installedModels.count === 0 || currentModelName() === "") && !currentChat.isServer
visible: !currentChat.isModelLoaded && (ModelList.selectableModels.count === 0 || currentModelName() === "") && !currentChat.isServer
ColumnLayout {
visible: ModelList.installedModels.count !== 0
visible: ModelList.selectableModels.count !== 0
id: modelInstalledLabel
anchors.centerIn: parent
spacing: 0
@@ -632,13 +672,24 @@ Rectangle {
}
MyButton {
visible: ModelList.installedModels.count !== 0
id: loadDefaultModelButton
visible: ModelList.selectableModels.count !== 0
anchors.top: modelInstalledLabel.bottom
anchors.topMargin: 50
anchors.horizontalCenter: modelInstalledLabel.horizontalCenter
rightPadding: 60
leftPadding: 60
text: qsTr("Load default model \u2192");
property string defaultModel: ""
function updateDefaultModel() {
var i = comboBox.find(MySettings.userDefaultModel)
if (i !== -1) {
defaultModel = comboBox.valueAt(i);
} else {
defaultModel = comboBox.valueAt(0);
}
}
text: qsTr("Load \u00B7 ") + defaultModel + qsTr(" (default) \u2192");
onClicked: {
var i = comboBox.find(MySettings.userDefaultModel)
if (i !== -1) {
@@ -647,6 +698,20 @@ Rectangle {
comboBox.changeModel(0);
}
}
// This requires a bit of work because apparently the combobox valueAt
// function only works after the combobox component is loaded so we have
// to use our own component loaded to make this work along with a signal
// from MySettings for when the setting for user default model changes
Connections {
target: MySettings
function onUserDefaultModelChanged() {
loadDefaultModelButton.updateDefaultModel()
}
}
Component.onCompleted: {
loadDefaultModelButton.updateDefaultModel()
}
Accessible.role: Accessible.Button
Accessible.name: qsTr("Load the default model")
Accessible.description: qsTr("Loads the default model which can be changed in settings")
@@ -654,7 +719,7 @@ Rectangle {
ColumnLayout {
id: noModelInstalledLabel
visible: ModelList.installedModels.count === 0
visible: ModelList.selectableModels.count === 0
anchors.centerIn: parent
spacing: 0
@@ -675,7 +740,7 @@ Rectangle {
}
MyButton {
visible: ModelList.installedModels.count === 0
visible: ModelList.selectableModels.count === 0
anchors.top: noModelInstalledLabel.bottom
anchors.topMargin: 50
anchors.horizontalCenter: noModelInstalledLabel.horizontalCenter
@@ -692,7 +757,7 @@ Rectangle {
ColumnLayout {
anchors.fill: parent
visible: ModelList.installedModels.count !== 0 && chatModel.count !== 0
visible: ModelList.selectableModels.count !== 0 && chatModel.count !== 0
ListView {
id: listView
Layout.maximumWidth: 1280
@@ -824,7 +889,7 @@ Rectangle {
id: tapHandler
onTapped: function(eventPoint, button) {
var clickedPos = myTextArea.positionAt(eventPoint.position.x, eventPoint.position.y);
var success = responseText.tryCopyAtPosition(clickedPos);
var success = textProcessor.tryCopyAtPosition(clickedPos);
if (success)
copyCodeMessage.open();
}
@@ -844,6 +909,16 @@ Rectangle {
}
}
onLinkActivated: function(link) {
if (!currentResponse || !currentChat.responseInProgress)
Qt.openUrlExternally(link)
}
onLinkHovered: function (link) {
if (!currentResponse || !currentChat.responseInProgress)
statusBar.externalHoveredLink = link
}
Menu {
id: conversationContextMenu
MenuItem {
@@ -862,16 +937,24 @@ Rectangle {
myTextArea.deselect()
}
}
MenuItem {
text: textProcessor.shouldProcessText ? qsTr("Disable markdown") : qsTr("Enable markdown")
height: enabled ? implicitHeight : 0
onTriggered: {
textProcessor.shouldProcessText = !textProcessor.shouldProcessText;
myTextArea.text = value
}
}
}
ResponseText {
id: responseText
ChatViewTextProcessor {
id: textProcessor
}
Component.onCompleted: {
responseText.setLinkColor(theme.linkColor);
responseText.setHeaderColor(name === qsTr("Response: ") ? theme.darkContrast : theme.lightContrast);
responseText.textDocument = textDocument
textProcessor.setLinkColor(theme.linkColor);
textProcessor.setHeaderColor(name === qsTr("Response: ") ? theme.darkContrast : theme.lightContrast);
textProcessor.textDocument = textDocument
}
Accessible.role: Accessible.Paragraph
@@ -1000,10 +1083,9 @@ Rectangle {
}
}
TextArea {
text: consolidatedSources.length + " " + qsTr("Sources")
Text {
text: qsTr("%1 Sources").arg(consolidatedSources.length)
padding: 0
readOnly: true
font.pixelSize: theme.fontSizeLarge
font.bold: true
color: theme.styledTextColor
@@ -1048,6 +1130,7 @@ Rectangle {
id: sourcesLayout
Layout.row: 3
Layout.column: 1
Layout.topMargin: 5
visible: consolidatedSources.length !== 0 && MySettings.localDocsShowReferences && (!currentResponse || !currentChat.responseInProgress)
clip: true
Layout.fillWidth: true
@@ -1079,7 +1162,6 @@ Rectangle {
Flow {
Layout.fillWidth: true
Layout.topMargin: 5
spacing: 10
visible: consolidatedSources.length !== 0
Repeater {
@@ -1312,7 +1394,8 @@ Rectangle {
}
Text {
id: device
id: statusBar
property string externalHoveredLink: ""
anchors.top: textInputView.bottom
anchors.bottom: parent.bottom
anchors.right: parent.right
@@ -1322,10 +1405,13 @@ Rectangle {
horizontalAlignment: Qt.AlignRight
verticalAlignment: Qt.AlignVCenter
color: theme.mutedTextColor
visible: currentChat.tokenSpeed !== ""
visible: currentChat.tokenSpeed !== "" || externalHoveredLink !== ""
elide: Text.ElideRight
wrapMode: Text.WordWrap
text: {
if (externalHoveredLink !== "")
return externalHoveredLink
const segments = [currentChat.tokenSpeed];
const device = currentChat.device;
const backend = currentChat.deviceBackend;
@@ -1346,7 +1432,7 @@ Rectangle {
RectangularGlow {
id: effect
visible: !currentChat.isServer && ModelList.installedModels.count !== 0
visible: !currentChat.isServer && ModelList.selectableModels.count !== 0
anchors.fill: textInputView
glowRadius: 50
spread: 0
@@ -1364,12 +1450,12 @@ Rectangle {
anchors.leftMargin: Math.max((parent.width - 1310) / 2, 30)
anchors.rightMargin: Math.max((parent.width - 1310) / 2, 30)
height: Math.min(contentHeight, 200)
visible: !currentChat.isServer && ModelList.installedModels.count !== 0
visible: !currentChat.isServer && ModelList.selectableModels.count !== 0
MyTextArea {
id: textInput
color: theme.textColor
topPadding: 30
bottomPadding: 30
topPadding: 15
bottomPadding: 15
leftPadding: 20
rightPadding: 40
enabled: currentChat.isModelLoaded && !currentChat.isServer
@@ -1456,9 +1542,9 @@ Rectangle {
anchors.right: textInputView.right
anchors.verticalCenter: textInputView.verticalCenter
anchors.rightMargin: 15
width: 30
height: 30
visible: !currentChat.isServer && ModelList.installedModels.count !== 0
imageWidth: theme.fontSizeLarger
imageHeight: theme.fontSizeLarger
visible: !currentChat.isServer && ModelList.selectableModels.count !== 0
enabled: !currentChat.responseInProgress
source: "qrc:/gpt4all/icons/send_message.svg"
Accessible.name: qsTr("Send message")

View File

@@ -89,7 +89,7 @@ Rectangle {
text: "%1 %2".arg(qsTr("%n file(s)", "", model.totalDocs)).arg(qsTr("%n word(s)", "", model.totalWords))
elide: Text.ElideRight
color: theme.mutedTextColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
}
RowLayout {
visible: model.updating
@@ -105,7 +105,7 @@ Rectangle {
text: qsTr("Updating")
elide: Text.ElideRight
color: theme.accentColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
font.bold: true
}
}
@@ -136,7 +136,7 @@ Rectangle {
Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
text: qsTr("Chat privately with local files using on-device Large Language Models (LLMs). Keeps data private and secure. Best results with Llama 3 Instruct.")
text: qsTr("Select a collection to make it available to the chat model.")
font.pixelSize: theme.fontSizeLarger
wrapMode: Text.WordWrap
elide: Text.ElideRight

View File

@@ -36,6 +36,7 @@ Rectangle {
Layout.fillWidth: true
Layout.maximumWidth: 1530
Layout.alignment: Qt.AlignCenter
Layout.topMargin: 20
spacing: 30
ColumnLayout {
@@ -52,7 +53,7 @@ Rectangle {
Text {
Layout.alignment: Qt.AlignHCenter
text: qsTr("the privacy-first LLM chat application")
text: qsTr("The privacy-first LLM chat application")
font.pixelSize: theme.fontSizeLarge
color: theme.titleInfoTextColor
}

View File

@@ -19,20 +19,30 @@ MySettingsTab {
title: qsTr("LocalDocs")
contentItem: ColumnLayout {
id: root
spacing: 10
spacing: 30
Label {
color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarge
Layout.bottomMargin: 10
color: theme.settingsTitleTextColor
font.pixelSize: theme.fontSizeBannerSmall
font.bold: true
text: "Indexing"
text: qsTr("LocalDocs Settings")
}
Rectangle {
Layout.bottomMargin: 15
Layout.fillWidth: true
height: 1
color: theme.settingsDivider
ColumnLayout {
spacing: 10
Label {
color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: qsTr("Indexing")
}
Rectangle {
Layout.fillWidth: true
height: 1
color: theme.settingsDivider
}
}
RowLayout {
@@ -81,25 +91,26 @@ MySettingsTab {
}
}
Label {
Layout.topMargin: 15
color: theme.grayRed900
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: "Embedding"
}
ColumnLayout {
spacing: 10
Label {
color: theme.grayRed900
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: qsTr("Embedding")
}
Rectangle {
Layout.bottomMargin: 15
Layout.fillWidth: true
height: 1
color: theme.grayRed500
Rectangle {
Layout.fillWidth: true
height: 1
color: theme.grayRed500
}
}
RowLayout {
MySettingsLabel {
text: qsTr("Use Nomic Embed API")
helpText: qsTr("Embed documents using the fast Nomic API instead of a private local model.")
helpText: qsTr("Embed documents using the fast Nomic API instead of a private local model. Requires restart.")
}
MyCheckBox {
@@ -117,7 +128,7 @@ MySettingsTab {
MySettingsLabel {
id: apiKeyLabel
text: qsTr("Nomic API Key")
helpText: qsTr('API key to use for Nomic Embed. Get one from the Atlas <a href="https://atlas.nomic.ai/cli-login">API keys page</a>.')
helpText: qsTr('API key to use for Nomic Embed. Get one from the Atlas <a href="https://atlas.nomic.ai/cli-login">API keys page</a>. Requires restart.')
onLinkActivated: function(link) { Qt.openUrlExternally(link) }
}
@@ -148,26 +159,61 @@ MySettingsTab {
}
}
Label {
Layout.topMargin: 15
color: theme.grayRed900
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: "Display"
RowLayout {
MySettingsLabel {
id: deviceLabel
text: qsTr("Embeddings Device")
helpText: qsTr('The compute device used for embeddings. "Auto" uses the CPU. Requires restart.')
}
MyComboBox {
id: deviceBox
enabled: !useNomicAPIBox.checked
Layout.minimumWidth: 400
Layout.maximumWidth: 400
Layout.fillWidth: false
Layout.alignment: Qt.AlignRight
model: MySettings.embeddingsDeviceList
Accessible.name: deviceLabel.text
Accessible.description: deviceLabel.helpText
function updateModel() {
deviceBox.currentIndex = deviceBox.indexOfValue(MySettings.localDocsEmbedDevice);
}
Component.onCompleted: {
deviceBox.updateModel();
}
Connections {
target: MySettings
function onDeviceChanged() {
deviceBox.updateModel();
}
}
onActivated: {
MySettings.localDocsEmbedDevice = deviceBox.currentText;
}
}
}
Rectangle {
Layout.bottomMargin: 15
Layout.fillWidth: true
height: 1
color: theme.grayRed500
ColumnLayout {
spacing: 10
Label {
color: theme.grayRed900
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: qsTr("Display")
}
Rectangle {
Layout.fillWidth: true
height: 1
color: theme.grayRed500
}
}
RowLayout {
MySettingsLabel {
id: showReferencesLabel
text: qsTr("Show sources")
helpText: qsTr("Shows sources in GUI generated by localdocs")
text: qsTr("Show Sources")
helpText: qsTr("Display the sources used for each response.")
}
MyCheckBox {
id: showReferencesBox
@@ -178,19 +224,20 @@ MySettingsTab {
}
}
Label {
Layout.topMargin: 15
color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: "Advanced"
}
ColumnLayout {
spacing: 10
Label {
color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: qsTr("Advanced")
}
Rectangle {
Layout.bottomMargin: 15
Layout.fillWidth: true
height: 1
color: theme.settingsDivider
Rectangle {
Layout.fillWidth: true
height: 1
color: theme.settingsDivider
}
}
MySettingsLabel {

View File

@@ -68,7 +68,7 @@ Rectangle {
MyButton {
Layout.alignment: Qt.AlignTop | Qt.AlignRight
text: qsTr("\uFF0B Add Doc Collection")
text: qsTr("\uFF0B Add Collection")
onClicked: {
addCollectionViewRequested()
}
@@ -307,7 +307,7 @@ Rectangle {
}
elide: Text.ElideRight
color: theme.mutedDarkTextColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
}
Text {
visible: {
@@ -322,7 +322,7 @@ Rectangle {
elide: Text.ElideRight
color: theme.mutedDarkTextColor
font.family: "monospace"
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
}
}
@@ -332,21 +332,21 @@ Rectangle {
text: "%1 %2".arg(qsTr("%n file(s)", "", model.totalDocs)).arg(qsTr("%n word(s)", "", model.totalWords))
elide: Text.ElideRight
color: theme.styledTextColor2
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
}
Text {
text: model.embeddingModel
elide: Text.ElideRight
color: theme.mutedDarkTextColor
font.bold: true
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
}
Text {
visible: Qt.formatDateTime(model.lastUpdate) !== ""
text: Qt.formatDateTime(model.lastUpdate)
elide: Text.ElideRight
color: theme.mutedTextColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
}
Text {
visible: model.currentEmbeddingsToIndex !== 0
@@ -354,7 +354,7 @@ Rectangle {
+ model.totalEmbeddingsToIndex + " embeddings"
elide: Text.ElideRight
color: theme.mutedTextColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
}
}
@@ -399,54 +399,35 @@ Rectangle {
RowLayout {
Layout.fillWidth: true
spacing: 30
Layout.leftMargin: 15
Layout.topMargin: 15
Text {
MySettingsButton {
text: qsTr("Remove")
elide: Text.ElideRight
color: theme.red500
font.bold: true
font.pixelSize: theme.fontSizeSmall
TapHandler {
onTapped: {
LocalDocs.removeFolder(collection, folder_path)
}
}
}
Text {
Layout.alignment: Qt.AlignRight
visible: !model.forceIndexing && !model.indexing && model.currentEmbeddingsToIndex === 0
text: qsTr("Rebuild")
elide: Text.ElideRight
color: theme.red500
font.bold: true
font.pixelSize: theme.fontSizeSmall
TapHandler {
onTapped: { LocalDocs.forceRebuildFolder(folder_path); }
}
HoverHandler { id: hoverHandler1 }
ToolTip.text: qsTr("Reindex this folder from scratch. This is slow and usually not needed.")
ToolTip.visible: hoverHandler1.hovered
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
textColor: theme.red500
onClicked: LocalDocs.removeFolder(collection, folder_path)
backgroundColor: "transparent"
backgroundColorHovered: theme.lighterButtonBackgroundHoveredRed
}
Item {
Layout.fillWidth: true
}
Text {
Layout.alignment: Qt.AlignRight
MySettingsButton {
id: rebuildButton
visible: !model.forceIndexing && !model.indexing && model.currentEmbeddingsToIndex === 0
text: qsTr("Rebuild")
textColor: theme.green500
onClicked: LocalDocs.forceRebuildFolder(folder_path)
toolTip: qsTr("Reindex this folder from scratch. This is slow and usually not needed.")
backgroundColor: "transparent"
backgroundColorHovered: theme.lighterButtonBackgroundHovered
}
MySettingsButton {
id: updateButton
visible: model.forceIndexing
text: qsTr("Update")
elide: Text.ElideRight
color: theme.red500
font.bold: true
font.pixelSize: theme.fontSizeSmall
TapHandler {
onTapped: { LocalDocs.forceIndexing(collection); }
}
HoverHandler { id: hoverHandler2 }
ToolTip.text: qsTr("Update the collection to the new version. This is a slow operation.")
ToolTip.visible: hoverHandler2.hovered
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
textColor: theme.green500
onClicked: LocalDocs.forceIndexing(collection)
toolTip: qsTr("Update the collection to the new version. This is a slow operation.")
backgroundColor: "transparent"
backgroundColorHovered: theme.lighterButtonBackgroundHovered
}
}
}

View File

@@ -11,44 +11,26 @@ MySettingsTab {
onRestoreDefaultsClicked: {
MySettings.restoreModelDefaults(root.currentModelInfo);
}
title: qsTr("Model/Character Settings")
title: qsTr("Model")
contentItem: GridLayout {
id: root
columns: 3
rowSpacing: 10
columnSpacing: 10
enabled: ModelList.installedModels.count !== 0
enabled: ModelList.selectableModels.count !== 0
property var currentModelName: comboBox.currentText
property var currentModelId: comboBox.currentValue
property var currentModelInfo: ModelList.modelInfo(root.currentModelId)
ColumnLayout {
Layout.row: 0
Layout.column: 0
Layout.columnSpan: 3
Layout.fillWidth: true
spacing: 10
Label {
color: theme.styledTextColor
font.pixelSize: theme.fontSizeLarge
font.bold: true
text: "General"
}
Rectangle {
Layout.fillWidth: true
height: 1
color: theme.settingsDivider
}
}
MySettingsLabel {
id: label
Label {
Layout.row: 1
Layout.column: 0
text: qsTr("Model/Character")
helpText: qsTr("Select or clone a model and change its settings")
Layout.bottomMargin: 10
color: theme.settingsTitleTextColor
font.pixelSize: theme.fontSizeBannerSmall
font.bold: true
text: qsTr("Model Settings")
}
RowLayout {
@@ -56,13 +38,12 @@ MySettingsTab {
Layout.row: 2
Layout.column: 0
Layout.columnSpan: 2
height: label.height + 20
spacing: 10
MyComboBox {
id: comboBox
Layout.fillWidth: true
model: ModelList.installedModels
model: ModelList.selectableModels
valueRole: "id"
textRole: "name"
currentIndex: {
@@ -122,9 +103,7 @@ MySettingsTab {
Layout.topMargin: 15
spacing: 10
MySettingsLabel {
id: uniqueNameLabel
text: qsTr("Unique Name")
helpText: qsTr("Must contain a non-empty unique name")
text: qsTr("Name")
}
}
@@ -158,7 +137,6 @@ MySettingsTab {
MySettingsLabel {
text: qsTr("Model File")
helpText: qsTr("The filename of the selected model")
Layout.row: 5
Layout.column: 0
Layout.topMargin: 15
@@ -177,7 +155,7 @@ MySettingsTab {
MySettingsLabel {
visible: !root.currentModelInfo.isOnline
text: qsTr("System Prompt")
helpText: qsTr("Prefixed at the beginning of every conversation")
helpText: qsTr("Prefixed at the beginning of every conversation. Must contain the appropriate framing tokens.")
Layout.row: 7
Layout.column: 0
Layout.topMargin: 15
@@ -212,9 +190,6 @@ MySettingsTab {
MySettings.setModelSystemPrompt(root.currentModelInfo, text)
}
Accessible.role: Accessible.EditableText
ToolTip.text: qsTr("The systemPrompt allows instructions to the model at the beginning of a chat.\nNOTE: A longer, detailed system prompt can lead to higher quality answers, but can also slow down generation.")
ToolTip.visible: hovered
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
}
}
@@ -227,7 +202,7 @@ MySettingsTab {
MySettingsLabel {
id: promptTemplateLabel
text: qsTr("Prompt Template")
helpText: qsTr("The template that wraps every prompt")
helpText: qsTr("The template that wraps every prompt.")
}
MySettingsLabel {
id: promptTemplateLabelHelp
@@ -271,9 +246,6 @@ MySettingsTab {
Accessible.role: Accessible.EditableText
Accessible.name: promptTemplateLabel.text
Accessible.description: promptTemplateLabelHelp.text
ToolTip.text: qsTr("The prompt template partially determines how models will respond to prompts.\nNOTE: A longer, detailed template can lead to higher quality answers, but can also slow down generation.")
ToolTip.visible: hovered
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
}
}
@@ -328,7 +300,7 @@ MySettingsTab {
id: contextLengthLabel
visible: !root.currentModelInfo.isOnline
text: qsTr("Context Length")
helpText: qsTr("Conversation context window")
helpText: qsTr("Number of input and output tokens the model sees.")
Layout.row: 0
Layout.column: 0
}
@@ -378,7 +350,7 @@ MySettingsTab {
MySettingsLabel {
id: tempLabel
text: qsTr("Temperature")
helpText: qsTr("The temperature for model token generation")
helpText: qsTr("Randomness of model output. Higher -> more variation.")
Layout.row: 1
Layout.column: 2
}
@@ -422,8 +394,8 @@ MySettingsTab {
}
MySettingsLabel {
id: topPLabel
text: qsTr("Top P")
helpText: qsTr("Prevents choosing highly unlikely tokens")
text: qsTr("Top-P")
helpText: qsTr("Nucleus Sampling factor. Lower -> more predicatable.")
Layout.row: 2
Layout.column: 0
}
@@ -432,7 +404,7 @@ MySettingsTab {
text: root.currentModelInfo.topP
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
ToolTip.text: qsTr("Only the most likely tokens up to a total probability of top_p can be chosen.\nNOTE: Prevents choosing highly unlikely tokens, aka Nucleus Sampling")
ToolTip.text: qsTr("Only the most likely tokens up to a total probability of top_p can be chosen.\nNOTE: Prevents choosing highly unlikely tokens.")
ToolTip.visible: hovered
Layout.row: 2
Layout.column: 1
@@ -466,8 +438,8 @@ MySettingsTab {
}
MySettingsLabel {
id: minPLabel
text: qsTr("Min P")
helpText: qsTr("Minimum relative probability")
text: qsTr("Min-P")
helpText: qsTr("Minimum token probability. Higher -> more predictable.")
Layout.row: 3
Layout.column: 0
}
@@ -512,8 +484,8 @@ MySettingsTab {
MySettingsLabel {
id: topKLabel
visible: !root.currentModelInfo.isOnline
text: qsTr("Top K")
helpText: qsTr("Size of selection pool for tokens")
text: qsTr("Top-K")
helpText: qsTr("Size of selection pool for tokens.")
Layout.row: 2
Layout.column: 2
}
@@ -523,7 +495,7 @@ MySettingsTab {
text: root.currentModelInfo.topK
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
ToolTip.text: qsTr("Only the top K most likely tokens will be chosen from")
ToolTip.text: qsTr("Only the top K most likely tokens will be chosen from.")
ToolTip.visible: hovered
Layout.row: 2
Layout.column: 3
@@ -559,7 +531,7 @@ MySettingsTab {
id: maxLengthLabel
visible: !root.currentModelInfo.isOnline
text: qsTr("Max Length")
helpText: qsTr("Maximum length of response in tokens")
helpText: qsTr("Maximum response length, in tokens.")
Layout.row: 0
Layout.column: 2
}
@@ -569,8 +541,6 @@ MySettingsTab {
text: root.currentModelInfo.maxLength
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
ToolTip.text: qsTr("Maximum length of response in tokens")
ToolTip.visible: hovered
Layout.row: 0
Layout.column: 3
validator: IntValidator {
@@ -606,7 +576,7 @@ MySettingsTab {
id: batchSizeLabel
visible: !root.currentModelInfo.isOnline
text: qsTr("Prompt Batch Size")
helpText: qsTr("Amount of prompt tokens to process at once")
helpText: qsTr("The batch size used for prompt processing.")
Layout.row: 1
Layout.column: 0
}
@@ -616,7 +586,7 @@ MySettingsTab {
text: root.currentModelInfo.promptBatchSize
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
ToolTip.text: qsTr("Amount of prompt tokens to process at once.\nNOTE: Higher values can speed up reading prompts but will use more RAM")
ToolTip.text: qsTr("Amount of prompt tokens to process at once.\nNOTE: Higher values can speed up reading prompts but will use more RAM.")
ToolTip.visible: hovered
Layout.row: 1
Layout.column: 1
@@ -652,7 +622,7 @@ MySettingsTab {
id: repeatPenaltyLabel
visible: !root.currentModelInfo.isOnline
text: qsTr("Repeat Penalty")
helpText: qsTr("Penalize repetitiveness")
helpText: qsTr("Repetition penalty factor. Set to 1 to disable.")
Layout.row: 4
Layout.column: 2
}
@@ -662,8 +632,6 @@ MySettingsTab {
text: root.currentModelInfo.repeatPenalty
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
ToolTip.text: qsTr("Amount to penalize repetitiveness of the output")
ToolTip.visible: hovered
Layout.row: 4
Layout.column: 3
validator: DoubleValidator {
@@ -698,7 +666,7 @@ MySettingsTab {
id: repeatPenaltyTokensLabel
visible: !root.currentModelInfo.isOnline
text: qsTr("Repeat Penalty Tokens")
helpText: qsTr("Length to apply penalty")
helpText: qsTr("Number of previous tokens used for penalty.")
Layout.row: 3
Layout.column: 2
}
@@ -708,8 +676,6 @@ MySettingsTab {
text: root.currentModelInfo.repeatPenaltyTokens
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
ToolTip.text: qsTr("How far back in output to apply repeat penalty")
ToolTip.visible: hovered
Layout.row: 3
Layout.column: 3
validator: IntValidator {
@@ -745,7 +711,7 @@ MySettingsTab {
id: gpuLayersLabel
visible: !root.currentModelInfo.isOnline
text: qsTr("GPU Layers")
helpText: qsTr("How many GPU layers to load into VRAM")
helpText: qsTr("Number of model layers to load into VRAM.")
Layout.row: 4
Layout.column: 0
}
@@ -755,7 +721,7 @@ MySettingsTab {
text: root.currentModelInfo.gpuLayers
font.pixelSize: theme.fontSizeLarge
color: theme.textColor
ToolTip.text: qsTr("How many GPU layers to load into VRAM. Decrease this if GPT4All runs out of VRAM while loading this model.\nLower values increase CPU load and RAM usage, and make inference slower.\nNOTE: Does not take effect until you reload the model.")
ToolTip.text: qsTr("How many model layers to load into VRAM. Decrease this if GPT4All runs out of VRAM while loading this model.\nLower values increase CPU load and RAM usage, and make inference slower.\nNOTE: Does not take effect until you reload the model.")
ToolTip.visible: hovered
Layout.row: 4
Layout.column: 1

View File

@@ -82,7 +82,7 @@ Rectangle {
}
Text {
text: qsTr("Locally installed large language models")
text: qsTr("Locally installed chat models")
font.pixelSize: theme.fontSizeLarge
color: theme.titleInfoTextColor
}
@@ -150,20 +150,240 @@ Rectangle {
color: theme.dividerColor
}
Text {
id: descriptionText
text: description
font.pixelSize: theme.fontSizeLarge
Layout.row: 1
RowLayout {
Layout.topMargin: 10
wrapMode: Text.WordWrap
textFormat: Text.StyledText
color: theme.textColor
linkColor: theme.textColor
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Description")
Accessible.description: qsTr("File description")
onLinkActivated: Qt.openUrlExternally(link)
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
}
}
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") : qsTr("Resume")
font.pixelSize: theme.fontSizeLarge
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
visible: (isDownloading || isIncomplete) && downloadError === "" && !isOnline && !calcHash
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: {
if (apiKey.text === "")
apiKey.showError();
else
Download.installModel(filename, apiKey.text);
}
Accessible.role: Accessible.Button
Accessible.name: qsTr("Install")
Accessible.description: qsTr("Install online model")
}
ColumnLayout {
spacing: 0
Label {
Layout.topMargin: 20
Layout.leftMargin: 20
visible: downloadError !== ""
textFormat: Text.StyledText
text: "<strong><font size=\"1\">"
+ qsTr("<a href=\"#error\">Error</a>")
+ "</strong></font>"
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
linkColor: theme.textErrorColor
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: qsTr("Describes an error that occurred when downloading")
onLinkActivated: {
downloadingErrorPopup.text = downloadError;
downloadingErrorPopup.open();
}
}
Label {
visible: LLM.systemTotalRAMInGB() < ramrequired
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.maximumWidth: 300
textFormat: Text.StyledText
text: qsTr("<strong><font size=\"2\">WARNING: Not recommended for your hardware.")
+ qsTr(" Model requires more memory (") + ramrequired
+ qsTr(" GB) than your system has available (")
+ LLM.systemTotalRAMInGBString() + ").</strong></font>"
color: theme.textErrorColor
font.pixelSize: theme.fontSizeLarge
wrapMode: Text.WordWrap
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: qsTr("Error for incompatible hardware")
onLinkActivated: {
downloadingErrorPopup.text = downloadError;
downloadingErrorPopup.open();
}
}
}
ColumnLayout {
visible: isDownloading && !calcHash
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
spacing: 20
ProgressBar {
id: itemProgressBar
Layout.fillWidth: true
width: 200
value: bytesReceived / bytesTotal
background: Rectangle {
implicitHeight: 45
color: theme.progressBackground
radius: 3
}
contentItem: Item {
implicitHeight: 40
Rectangle {
width: itemProgressBar.visualPosition * parent.width
height: parent.height
radius: 2
color: theme.progressForeground
}
}
Accessible.role: Accessible.ProgressBar
Accessible.name: qsTr("Download progressBar")
Accessible.description: qsTr("Shows the progress made in the download")
}
Label {
id: speedLabel
color: theme.textColor
Layout.alignment: Qt.AlignRight
text: speed
font.pixelSize: theme.fontSizeLarge
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Download speed")
Accessible.description: qsTr("Download speed in bytes/kilobytes/megabytes per second")
}
}
RowLayout {
visible: calcHash
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.maximumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
clip: true
Label {
id: calcHashLabel
color: theme.textColor
text: qsTr("Calculating...")
font.pixelSize: theme.fontSizeLarge
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: qsTr("Whether the file hash is being calculated")
}
MyBusyIndicator {
id: busyCalcHash
running: calcHash
Accessible.role: Accessible.Animation
Accessible.name: qsTr("Busy indicator")
Accessible.description: qsTr("Displayed when the file hash is being calculated")
}
}
MyTextField {
id: apiKey
visible: !installed && isOnline
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
wrapMode: Text.WrapAnywhere
function showError() {
apiKey.placeholderTextColor = theme.textErrorColor
}
onTextChanged: {
apiKey.placeholderTextColor = theme.mutedTextColor
}
placeholderText: qsTr("enter $API_KEY")
Accessible.role: Accessible.EditableText
Accessible.name: placeholderText
Accessible.description: qsTr("Whether the file hash is being calculated")
}
}
}
}
Item {
@@ -180,13 +400,13 @@ Rectangle {
Layout.rightMargin: 20
Text {
text: qsTr("File size")
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
color: theme.mutedDarkTextColor
}
Text {
text: filesize
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
font.bold: true
}
}
@@ -202,13 +422,13 @@ Rectangle {
Layout.rightMargin: 20
Text {
text: qsTr("RAM required")
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
color: theme.mutedDarkTextColor
}
Text {
text: ramrequired + qsTr(" GB")
text: ramrequired >= 0 ? ramrequired + qsTr(" GB") : "?"
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
font.bold: true
}
}
@@ -224,13 +444,13 @@ Rectangle {
Layout.rightMargin: 20
Text {
text: qsTr("Parameters")
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
color: theme.mutedDarkTextColor
}
Text {
text: parameters
text: parameters !== "" ? parameters : "?"
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
font.bold: true
}
}
@@ -246,13 +466,13 @@ Rectangle {
Layout.rightMargin: 20
Text {
text: qsTr("Quant")
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
color: theme.mutedDarkTextColor
}
Text {
text: quant
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
font.bold: true
}
}
@@ -268,13 +488,13 @@ Rectangle {
Layout.rightMargin: 20
Text {
text: qsTr("Type")
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
color: theme.mutedDarkTextColor
}
Text {
text: type
color: theme.textColor
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeSmall
font.bold: true
}
}
@@ -294,25 +514,6 @@ Rectangle {
height: 1
color: theme.dividerColor
}
RowLayout {
Layout.fillWidth: true
spacing: 30
Layout.leftMargin: 15
Layout.topMargin: 15
Text {
text: qsTr("Remove")
elide: Text.ElideRight
color: theme.red500
font.bold: true
font.pixelSize: theme.fontSizeSmall
TapHandler {
onTapped: {
Download.removeModel(filename);
}
}
}
}
}
}
}

View File

@@ -1,6 +1,8 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
ComboBox {
id: comboBox
@@ -8,15 +10,39 @@ ComboBox {
spacing: 0
padding: 10
Accessible.role: Accessible.ComboBox
contentItem: Text {
id: text
leftPadding: 10
rightPadding: 20
text: comboBox.displayText
font: comboBox.font
color: theme.textColor
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
contentItem: RowLayout {
id: contentRow
spacing: 0
Text {
id: text
Layout.fillWidth: true
leftPadding: 10
rightPadding: 20
text: comboBox.displayText
font: comboBox.font
color: theme.textColor
verticalAlignment: Text.AlignLeft
elide: Text.ElideRight
}
Item {
Layout.preferredWidth: updown.width
Layout.preferredHeight: updown.height
Image {
id: updown
anchors.verticalCenter: parent.verticalCenter
sourceSize.width: comboBox.font.pixelSize
sourceSize.height: comboBox.font.pixelSize
mipmap: true
visible: false
source: "qrc:/gpt4all/icons/up_down.svg"
}
ColorOverlay {
anchors.fill: updown
source: updown
color: theme.textColor
}
}
}
delegate: ItemDelegate {
width: comboBox.width
@@ -53,33 +79,7 @@ ComboBox {
color: theme.black
}
}
indicator: Canvas {
id: canvas
x: comboBox.width - width - comboBox.rightPadding
y: comboBox.topPadding + (comboBox.availableHeight - height) / 2
width: 12
height: 18
contextType: "2d"
Connections {
target: comboBox
function onPressedChanged() { canvas.requestPaint(); }
}
onPaint: {
var context = getContext("2d");
context.reset();
context.lineWidth = 2;
context.moveTo(0, height / 2 - 2);
context.lineTo(width / 2, 0);
context.lineTo(width, height / 2 - 2);
context.moveTo(0, height / 2 + 2);
context.lineTo(width / 2, height);
context.lineTo(width, height / 2 + 2);
context.strokeStyle = comboBox.pressed ? theme.mutedLightTextColor : theme.mutedLighterTextColor;
context.stroke();
}
indicator: Item {
}
background: Rectangle {
color: theme.controlBackground

View File

@@ -33,8 +33,8 @@ Button {
anchors.centerIn: parent
visible: false
mipmap: true
sourceSize.width: 20
sourceSize.height: 20
sourceSize.width: 16
sourceSize.height: 16
}
ColorOverlay {
anchors.fill: image

View File

@@ -9,13 +9,14 @@ Button {
padding: 10
rightPadding: 18
leftPadding: 18
property color textColor: theme.lightButtonText
property color mutedTextColor: theme.lightButtonMutedText
property color backgroundColor: theme.lightButtonBackground
property color backgroundColorHovered: enabled ? theme.lightButtonBackgroundHovered : backgroundColor
property real borderWidth: 0
property color borderColor: "transparent"
property real fontPixelSize: theme.fontSizeLarge
property color textColor: theme.lightButtonText
property color mutedTextColor: theme.lightButtonMutedText
property color backgroundColor: theme.lightButtonBackground
property color backgroundColorHovered: enabled ? theme.lightButtonBackgroundHovered : backgroundColor
property real borderWidth: 0
property color borderColor: "transparent"
property real fontPixelSize: theme.fontSizeLarge
property string toolTip
contentItem: Text {
text: myButton.text
@@ -34,5 +35,7 @@ Button {
}
Accessible.role: Accessible.Button
Accessible.name: text
ToolTip.text: toolTip
ToolTip.visible: toolTip !== "" && myButton.hovered
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
}

View File

@@ -20,7 +20,7 @@ ColumnLayout {
Label {
id: mainTextLabel
color: theme.settingsTitleTextColor
font.pixelSize: theme.fontSizeSmall
font.pixelSize: theme.fontSizeLarger
font.bold: true
onLinkActivated: function(link) {
root.linkActivated(link);
@@ -28,12 +28,13 @@ ColumnLayout {
}
Label {
id: helpTextLabel
visible: text !== ""
Layout.fillWidth: true
wrapMode: Text.Wrap
color: theme.settingsTitleTextColor
text: mainTextLabel.text
font.pixelSize: theme.fontSizeSmaller
font.pixelSize: theme.fontSizeLarge
font.bold: false
onLinkActivated: function(link) {
root.linkActivated(link);
}

View File

@@ -0,0 +1,19 @@
import QtQuick
import QtQuick.Controls
Text {
id: text
signal click()
property string tooltip
HoverHandler { id: hoverHandler }
TapHandler { onTapped: { click() } }
font.bold: true
font.underline: hoverHandler.hovered
font.pixelSize: theme.fontSizeSmall
ToolTip.text: tooltip
ToolTip.visible: tooltip !== "" && hoverHandler.hovered
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
}

View File

@@ -33,7 +33,7 @@ Button {
visible: myButton.toggled
border.color: myButton.toggledColor
border.width: myButton.toggledWidth
radius: 10
radius: 8
}
Image {
id: image

View File

@@ -30,7 +30,7 @@ MyDialog {
anchors.left: parent.left
width: 60
height: 60
source: "qrc:/gpt4all/icons/logo.svg"
source: "qrc:/gpt4all/icons/gpt4all.svg"
}
Text {
anchors.left: img.right

View File

@@ -467,6 +467,17 @@ QtObject {
}
}
property color lighterButtonBackgroundHoveredRed: {
switch (MySettings.chatTheme) {
case "LegacyDark":
return buttonBackgroundHovered;
case "Dark":
return buttonBackgroundHovered;
default:
return red50;
}
}
property color sourcesBackground: {
switch (MySettings.chatTheme) {
case "LegacyDark":
@@ -1058,19 +1069,17 @@ QtObject {
}
}
property real fontSizeLargeCapped: MySettings.fontSize === "Small" ? 12 : 17
property real fontScale: MySettings.fontSize === "Small" ? 1 :
MySettings.fontSize === "Medium" ? 1.3 :
/* "Large" */ 1.8
property real fontSizeLarge: MySettings.fontSize === "Small" ? 12 :
MySettings.fontSize === "Medium" ? 17 :
22
property real fontSizeLargest: MySettings.fontSize === "Small" ? 19 :
MySettings.fontSize === "Medium" ? 24 :
26
property real fontSizeSmaller: fontSizeLarge - 4
property real fontSizeSmall: fontSizeLarge - 2
property real fontSizeLarger: fontSizeLarge + 2
property real fontSizeBannerSmall: fontSizeLargest + 10
property real fontSizeBanner: fontSizeLargest + 40
property real fontSizeSmallest: 8 * fontScale
property real fontSizeSmaller: 9 * fontScale
property real fontSizeSmall: 10 * fontScale
property real fontSizeMedium: 11 * fontScale
property real fontSizeLarge: 12 * fontScale
property real fontSizeLarger: 14 * fontScale
property real fontSizeLargest: 18 * fontScale
property real fontSizeBannerSmall: 24 * fontScale**.8
property real fontSizeBanner: 48 * fontScale**.8
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 320 KiB