Compare commits
14 Commits
v3.0.0-rc2
...
v3.0.0-rc3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
720ea5a147 | ||
|
|
a191844a3f | ||
|
|
22396a6fa1 | ||
|
|
f2cad6abaa | ||
|
|
d893a6e5d6 | ||
|
|
8fe73832a6 | ||
|
|
23e8b187a4 | ||
|
|
dc6d01a0bb | ||
|
|
2c8d634b5b | ||
|
|
426aa5eb47 | ||
|
|
81bbeef5b3 | ||
|
|
1712830228 | ||
|
|
f6f265f968 | ||
|
|
6f52f602ef |
@@ -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 \
|
||||
|
||||
@@ -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}-rc3")
|
||||
|
||||
# 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
|
||||
@@ -208,9 +210,9 @@ qt_add_qml_module(chat
|
||||
icons/twitter.svg
|
||||
icons/left_panel_closed.svg
|
||||
icons/left_panel_open.svg
|
||||
icons/logo.svg
|
||||
icons/logo-32.png
|
||||
icons/logo-48.png
|
||||
icons/gpt4all.svg
|
||||
icons/gpt4all-32.png
|
||||
icons/gpt4all-48.png
|
||||
icons/you.svg
|
||||
icons/alt_logo.svg
|
||||
)
|
||||
@@ -219,6 +221,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 +238,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 +284,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 +330,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 +405,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 +415,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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -868,31 +871,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 © : m_copies) {
|
||||
if (position >= copy.startPos && position <= copy.endPos) {
|
||||
@@ -904,9 +908,67 @@ 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 *rootFrame = doc->rootFrame();
|
||||
QTextFrame::iterator rootIt;
|
||||
|
||||
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();
|
||||
|
||||
// Iterate over blocks within the frame
|
||||
QTextFrame::iterator frameIt;
|
||||
for (frameIt = childFrame->begin(); !frameIt.atEnd(); ++frameIt) {
|
||||
QTextBlock block = frameIt.currentBlock();
|
||||
if (block.isValid()) {
|
||||
qDebug() << " Block position:" << block.position();
|
||||
qDebug() << " Block text:" << block.text();
|
||||
|
||||
// Iterate over lines within the block
|
||||
for (QTextBlock::iterator blockIt = block.begin(); !(blockIt.atEnd()); ++blockIt) {
|
||||
QTextFragment fragment = blockIt.fragment();
|
||||
if (fragment.isValid()) {
|
||||
qDebug() << " Fragment text:" << fragment.text();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (childBlock.isValid()) {
|
||||
qDebug() << "Block position:" << childBlock.position();
|
||||
qDebug() << "Block text:" << 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -1100,12 +1146,68 @@ void ResponseText::handleCodeBlocks()
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
BIN
gpt4all-chat/icons/gpt4all-32.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
gpt4all-chat/icons/gpt4all-48.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
34
gpt4all-chat/icons/gpt4all.svg
Normal 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 |
|
Before Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 735 B |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 34 KiB |
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,8 +242,8 @@ Rectangle {
|
||||
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"
|
||||
|
||||
@@ -297,7 +297,7 @@ Rectangle {
|
||||
return 25
|
||||
}
|
||||
text: {
|
||||
if (ModelList.installedModels.count === 0)
|
||||
if (ModelList.selectableModels.count === 0)
|
||||
return qsTr("No model installed...")
|
||||
if (currentChat.modelLoadingError !== "")
|
||||
return qsTr("Model loading error...")
|
||||
@@ -365,7 +365,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.ComboBox
|
||||
Accessible.name: currentModelName()
|
||||
Accessible.description: qsTr("The top item is the current model")
|
||||
onActivated: function (index) {
|
||||
@@ -504,7 +503,12 @@ Rectangle {
|
||||
|
||||
background: Rectangle {
|
||||
radius: collectionsButton.backgroundRadius
|
||||
color: collectionsButton.toggled ? collectionsButton.backgroundColorHovered : collectionsButton.backgroundColor
|
||||
// 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")
|
||||
@@ -598,10 +602,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 +636,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 +662,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 +683,7 @@ Rectangle {
|
||||
|
||||
ColumnLayout {
|
||||
id: noModelInstalledLabel
|
||||
visible: ModelList.installedModels.count === 0
|
||||
visible: ModelList.selectableModels.count === 0
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
|
||||
@@ -675,7 +704,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 +721,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 +853,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 +873,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 +901,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 +1047,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 +1094,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 +1126,6 @@ Rectangle {
|
||||
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 5
|
||||
spacing: 10
|
||||
visible: consolidatedSources.length !== 0
|
||||
Repeater {
|
||||
@@ -1312,7 +1358,8 @@ Rectangle {
|
||||
}
|
||||
|
||||
Text {
|
||||
id: device
|
||||
id: statusBar
|
||||
property string externalHoveredLink: ""
|
||||
anchors.top: textInputView.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
@@ -1322,10 +1369,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 +1396,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,7 +1414,7 @@ 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
|
||||
@@ -1458,7 +1508,7 @@ Rectangle {
|
||||
anchors.rightMargin: 15
|
||||
width: 30
|
||||
height: 30
|
||||
visible: !currentChat.isServer && ModelList.installedModels.count !== 0
|
||||
visible: !currentChat.isServer && ModelList.selectableModels.count !== 0
|
||||
enabled: !currentChat.responseInProgress
|
||||
source: "qrc:/gpt4all/icons/send_message.svg"
|
||||
Accessible.name: qsTr("Send message")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
19
gpt4all-chat/qml/MyTextButton.qml
Normal 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
|
||||
}
|
||||
@@ -33,7 +33,7 @@ Button {
|
||||
visible: myButton.toggled
|
||||
border.color: myButton.toggledColor
|
||||
border.width: myButton.toggledWidth
|
||||
radius: 10
|
||||
radius: 8
|
||||
}
|
||||
Image {
|
||||
id: image
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 320 KiB |