mirror of
https://github.com/nomic-ai/gpt4all.git
synced 2026-07-17 10:58:08 +00:00
Compare commits
30 Commits
circleci_f
...
brave_sear
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ae6acdedc | ||
|
|
3a564688b1 | ||
|
|
054ca43d52 | ||
|
|
4cb95694ff | ||
|
|
991afc6ef2 | ||
|
|
75dbf9de7d | ||
|
|
f118720717 | ||
|
|
a6730879dd | ||
|
|
48117cda46 | ||
|
|
227dbfd18b | ||
|
|
587dd55b73 | ||
|
|
c3cfaff803 | ||
|
|
00ecbb75b4 | ||
|
|
f93b76438e | ||
|
|
cedba6cd10 | ||
|
|
244b82622c | ||
|
|
5fc2ff8e69 | ||
|
|
27b86dae21 | ||
|
|
01f67c74ea | ||
|
|
dfe3e951d4 | ||
|
|
fffd9f341a | ||
|
|
7c7558eed3 | ||
|
|
b9684ff741 | ||
|
|
b0578e28b9 | ||
|
|
d2ee235388 | ||
|
|
dda59a97a6 | ||
|
|
c78c95ab42 | ||
|
|
1c9911a4ac | ||
|
|
a71db10124 | ||
|
|
f4fb3df437 |
5
.gitmodules
vendored
5
.gitmodules
vendored
@@ -3,5 +3,8 @@
|
||||
url = https://github.com/nomic-ai/llama.cpp.git
|
||||
branch = master
|
||||
[submodule "gpt4all-chat/usearch"]
|
||||
path = gpt4all-chat/usearch
|
||||
path = gpt4all-chat/third_party/usearch
|
||||
url = https://github.com/nomic-ai/usearch.git
|
||||
[submodule "gpt4all-chat/third_party/jinja2cpp"]
|
||||
path = gpt4all-chat/third_party/jinja2cpp
|
||||
url = https://github.com/nomic-ai/jinja2cpp.git
|
||||
|
||||
@@ -107,6 +107,7 @@ endif()
|
||||
|
||||
qt_add_executable(chat
|
||||
main.cpp
|
||||
bravesearch.h bravesearch.cpp
|
||||
chat.h chat.cpp
|
||||
chatllm.h chatllm.cpp
|
||||
chatmodel.h chatlistmodel.h chatlistmodel.cpp
|
||||
@@ -115,13 +116,15 @@ qt_add_executable(chat
|
||||
database.h database.cpp
|
||||
download.h download.cpp
|
||||
embllm.cpp embllm.h
|
||||
localdocs.h localdocs.cpp localdocsmodel.h localdocsmodel.cpp
|
||||
localdocs.h localdocs.cpp localdocsmodel.h localdocsmodel.cpp localdocssearch.h localdocssearch.cpp
|
||||
llm.h llm.cpp
|
||||
modellist.h modellist.cpp
|
||||
mysettings.h mysettings.cpp
|
||||
network.h network.cpp
|
||||
sourceexcerpt.h sourceexcerpt.cpp
|
||||
server.h server.cpp
|
||||
logger.h logger.cpp
|
||||
tool.h tool.cpp toolmodel.h toolmodel.cpp
|
||||
${APP_ICON_RESOURCE}
|
||||
${CHAT_EXE_RESOURCES}
|
||||
)
|
||||
@@ -174,6 +177,7 @@ qt_add_qml_module(chat
|
||||
qml/MyTextField.qml
|
||||
qml/MyToolButton.qml
|
||||
qml/MyWelcomeButton.qml
|
||||
qml/WebSearchSettings.qml
|
||||
RESOURCES
|
||||
icons/antenna_1.svg
|
||||
icons/antenna_2.svg
|
||||
@@ -289,8 +293,10 @@ target_compile_definitions(chat
|
||||
# usearch uses the identifier 'slots' which conflicts with Qt's 'slots' keyword
|
||||
target_compile_definitions(chat PRIVATE QT_NO_SIGNALS_SLOTS_KEYWORDS)
|
||||
|
||||
target_include_directories(chat PRIVATE usearch/include
|
||||
usearch/fp16/include)
|
||||
target_include_directories(chat PRIVATE third_party/usearch/include
|
||||
third_party/usearch/fp16/include)
|
||||
|
||||
add_subdirectory(third_party/jinja2cpp ${CMAKE_BINARY_DIR}/jinja2cpp)
|
||||
|
||||
if(LINUX)
|
||||
target_link_libraries(chat
|
||||
@@ -300,7 +306,7 @@ else()
|
||||
PRIVATE Qt6::Quick Qt6::Svg Qt6::HttpServer Qt6::Sql Qt6::Pdf)
|
||||
endif()
|
||||
target_link_libraries(chat
|
||||
PRIVATE llmodel)
|
||||
PRIVATE llmodel jinja2cpp)
|
||||
|
||||
|
||||
# -- install --
|
||||
|
||||
234
gpt4all-chat/bravesearch.cpp
Normal file
234
gpt4all-chat/bravesearch.cpp
Normal file
@@ -0,0 +1,234 @@
|
||||
#include "bravesearch.h"
|
||||
#include "mysettings.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QGuiApplication>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QThread>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
BraveSearch::BraveSearch()
|
||||
: Tool(), m_error(ToolEnums::Error::NoError)
|
||||
{
|
||||
connect(MySettings::globalInstance(), &MySettings::webSearchUsageModeChanged,
|
||||
this, &Tool::usageModeChanged);
|
||||
connect(MySettings::globalInstance(), &MySettings::webSearchConfirmationModeChanged,
|
||||
this, &Tool::confirmationModeChanged);
|
||||
}
|
||||
|
||||
QString BraveSearch::run(const QJsonObject ¶meters, qint64 timeout)
|
||||
{
|
||||
// Reset the error state
|
||||
m_error = ToolEnums::Error::NoError;
|
||||
m_errorString = QString();
|
||||
|
||||
const QString apiKey = MySettings::globalInstance()->braveSearchAPIKey();
|
||||
const QString query = parameters["query"].toString();
|
||||
const int count = MySettings::globalInstance()->webSearchRetrievalSize();
|
||||
QThread workerThread;
|
||||
BraveAPIWorker worker;
|
||||
worker.moveToThread(&workerThread);
|
||||
connect(&worker, &BraveAPIWorker::finished, &workerThread, &QThread::quit, Qt::DirectConnection);
|
||||
connect(&workerThread, &QThread::started, [&worker, apiKey, query, count]() {
|
||||
worker.request(apiKey, query, count);
|
||||
});
|
||||
workerThread.start();
|
||||
bool timedOut = !workerThread.wait(timeout);
|
||||
if (timedOut) {
|
||||
m_error = ToolEnums::Error::TimeoutError;
|
||||
m_errorString = tr("ERROR: brave search timeout");
|
||||
} else {
|
||||
m_error = worker.error();
|
||||
m_errorString = worker.errorString();
|
||||
}
|
||||
workerThread.quit();
|
||||
workerThread.wait();
|
||||
return worker.response();
|
||||
}
|
||||
|
||||
QJsonObject BraveSearch::paramSchema() const
|
||||
{
|
||||
static const QString braveParamSchema = R"({
|
||||
"apiKey": {
|
||||
"type": "string",
|
||||
"description": "The api key to use",
|
||||
"required": true,
|
||||
"modelGenerated": false,
|
||||
"userConfigured": true
|
||||
},
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "The query to search",
|
||||
"required": true
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"description": "The number of excerpts to return",
|
||||
"required": true,
|
||||
"modelGenerated": false
|
||||
}
|
||||
})";
|
||||
|
||||
static const QJsonDocument braveJsonDoc = QJsonDocument::fromJson(braveParamSchema.toUtf8());
|
||||
Q_ASSERT(!braveJsonDoc.isNull() && braveJsonDoc.isObject());
|
||||
return braveJsonDoc.object();
|
||||
}
|
||||
|
||||
QJsonObject BraveSearch::exampleParams() const
|
||||
{
|
||||
static const QString example = R"({
|
||||
"query": "the 44th president of the United States"
|
||||
})";
|
||||
static const QJsonDocument exampleDoc = QJsonDocument::fromJson(example.toUtf8());
|
||||
Q_ASSERT(!exampleDoc.isNull() && exampleDoc.isObject());
|
||||
return exampleDoc.object();
|
||||
}
|
||||
|
||||
ToolEnums::UsageMode BraveSearch::usageMode() const
|
||||
{
|
||||
return MySettings::globalInstance()->webSearchUsageMode();
|
||||
}
|
||||
|
||||
ToolEnums::ConfirmationMode BraveSearch::confirmationMode() const
|
||||
{
|
||||
return MySettings::globalInstance()->webSearchConfirmationMode();
|
||||
}
|
||||
|
||||
void BraveAPIWorker::request(const QString &apiKey, const QString &query, int count)
|
||||
{
|
||||
// Documentation on the brave web search:
|
||||
// https://api.search.brave.com/app/documentation/web-search/get-started
|
||||
QUrl jsonUrl("https://api.search.brave.com/res/v1/web/search");
|
||||
|
||||
// Documentation on the query options:
|
||||
//https://api.search.brave.com/app/documentation/web-search/query
|
||||
QUrlQuery urlQuery;
|
||||
urlQuery.addQueryItem("q", query);
|
||||
urlQuery.addQueryItem("count", QString::number(count));
|
||||
urlQuery.addQueryItem("result_filter", "web");
|
||||
urlQuery.addQueryItem("extra_snippets", "true");
|
||||
jsonUrl.setQuery(urlQuery);
|
||||
QNetworkRequest request(jsonUrl);
|
||||
QSslConfiguration conf = request.sslConfiguration();
|
||||
conf.setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
request.setSslConfiguration(conf);
|
||||
request.setRawHeader("X-Subscription-Token", apiKey.toUtf8());
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
m_networkManager = new QNetworkAccessManager(this);
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
connect(qGuiApp, &QCoreApplication::aboutToQuit, reply, &QNetworkReply::abort);
|
||||
connect(reply, &QNetworkReply::finished, this, &BraveAPIWorker::handleFinished);
|
||||
connect(reply, &QNetworkReply::errorOccurred, this, &BraveAPIWorker::handleErrorOccurred);
|
||||
}
|
||||
|
||||
QString BraveAPIWorker::cleanBraveResponse(const QByteArray& jsonResponse)
|
||||
{
|
||||
// This parses the response from brave and formats it in json that conforms to the de facto
|
||||
// standard in SourceExcerpts::fromJson(...)
|
||||
QJsonParseError err;
|
||||
QJsonDocument document = QJsonDocument::fromJson(jsonResponse, &err);
|
||||
if (err.error != QJsonParseError::NoError) {
|
||||
m_error = ToolEnums::Error::UnknownError;
|
||||
m_errorString = QString(tr("ERROR: brave search could not parse json response: %1")).arg(jsonResponse);
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString query;
|
||||
QJsonObject searchResponse = document.object();
|
||||
QJsonArray cleanArray;
|
||||
|
||||
if (searchResponse.contains("query")) {
|
||||
QJsonObject queryObj = searchResponse["query"].toObject();
|
||||
if (queryObj.contains("original"))
|
||||
query = queryObj["original"].toString();
|
||||
}
|
||||
|
||||
if (searchResponse.contains("mixed")) {
|
||||
QJsonObject mixedResults = searchResponse["mixed"].toObject();
|
||||
QJsonArray mainResults = mixedResults["main"].toArray();
|
||||
QJsonObject resultsObject = searchResponse["web"].toObject();
|
||||
QJsonArray resultsArray = resultsObject["results"].toArray();
|
||||
|
||||
for (int i = 0; i < std::min(mainResults.size(), resultsArray.size()); ++i) {
|
||||
QJsonObject m = mainResults[i].toObject();
|
||||
QString r_type = m["type"].toString();
|
||||
Q_ASSERT(r_type == "web");
|
||||
const int idx = m["index"].toInt();
|
||||
|
||||
QJsonObject resultObj = resultsArray[idx].toObject();
|
||||
QStringList selectedKeys = {"type", "title", "url"};
|
||||
QJsonObject result;
|
||||
for (const auto& key : selectedKeys)
|
||||
if (resultObj.contains(key))
|
||||
result.insert(key, resultObj[key]);
|
||||
|
||||
if (resultObj.contains("page_age"))
|
||||
result.insert("date", resultObj["page_age"]);
|
||||
else
|
||||
result.insert("date", QDate::currentDate().toString());
|
||||
|
||||
QJsonArray excerpts;
|
||||
if (resultObj.contains("extra_snippets")) {
|
||||
QJsonArray snippets = resultObj["extra_snippets"].toArray();
|
||||
for (int i = 0; i < snippets.size(); ++i) {
|
||||
QString snippet = snippets[i].toString();
|
||||
QJsonObject excerpt;
|
||||
excerpt.insert("text", snippet);
|
||||
excerpts.append(excerpt);
|
||||
}
|
||||
if (resultObj.contains("description"))
|
||||
result.insert("description", resultObj["description"]);
|
||||
} else {
|
||||
QJsonObject excerpt;
|
||||
excerpt.insert("text", resultObj["description"]);
|
||||
}
|
||||
if (!excerpts.isEmpty()) {
|
||||
result.insert("excerpts", excerpts);
|
||||
cleanArray.append(QJsonValue(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject cleanResponse;
|
||||
cleanResponse.insert("query", query);
|
||||
cleanResponse.insert("results", cleanArray);
|
||||
QJsonDocument cleanedDoc(cleanResponse);
|
||||
// qDebug().noquote() << document.toJson(QJsonDocument::Indented);
|
||||
// qDebug().noquote() << cleanedDoc.toJson(QJsonDocument::Indented);
|
||||
return cleanedDoc.toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
void BraveAPIWorker::handleFinished()
|
||||
{
|
||||
QNetworkReply *jsonReply = qobject_cast<QNetworkReply *>(sender());
|
||||
Q_ASSERT(jsonReply);
|
||||
|
||||
if (jsonReply->error() == QNetworkReply::NoError && jsonReply->isFinished()) {
|
||||
QByteArray jsonData = jsonReply->readAll();
|
||||
jsonReply->deleteLater();
|
||||
m_response = cleanBraveResponse(jsonData);
|
||||
} else {
|
||||
QByteArray jsonData = jsonReply->readAll();
|
||||
jsonReply->deleteLater();
|
||||
}
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void BraveAPIWorker::handleErrorOccurred(QNetworkReply::NetworkError code)
|
||||
{
|
||||
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
||||
Q_ASSERT(reply);
|
||||
m_error = ToolEnums::Error::UnknownError;
|
||||
m_errorString = QString(tr("ERROR: brave search code: %1 response: %2")).arg(code).arg(reply->errorString());
|
||||
emit finished();
|
||||
}
|
||||
67
gpt4all-chat/bravesearch.h
Normal file
67
gpt4all-chat/bravesearch.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#ifndef BRAVESEARCH_H
|
||||
#define BRAVESEARCH_H
|
||||
|
||||
#include "tool.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
class BraveAPIWorker : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
BraveAPIWorker()
|
||||
: QObject(nullptr)
|
||||
, m_networkManager(nullptr) {}
|
||||
virtual ~BraveAPIWorker() {}
|
||||
|
||||
QString response() const { return m_response; }
|
||||
ToolEnums::Error error() const { return m_error; }
|
||||
QString errorString() const { return m_errorString; }
|
||||
|
||||
public Q_SLOTS:
|
||||
void request(const QString &apiKey, const QString &query, int count);
|
||||
|
||||
Q_SIGNALS:
|
||||
void finished();
|
||||
|
||||
private Q_SLOTS:
|
||||
QString cleanBraveResponse(const QByteArray& jsonResponse);
|
||||
void handleFinished();
|
||||
void handleErrorOccurred(QNetworkReply::NetworkError code);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QString m_response;
|
||||
ToolEnums::Error m_error = ToolEnums::Error::NoError;
|
||||
QString m_errorString;
|
||||
};
|
||||
|
||||
class BraveSearch : public Tool {
|
||||
Q_OBJECT
|
||||
public:
|
||||
BraveSearch();
|
||||
virtual ~BraveSearch() {}
|
||||
|
||||
QString run(const QJsonObject ¶meters, qint64 timeout = 2000) override;
|
||||
ToolEnums::Error error() const override { return m_error; }
|
||||
QString errorString() const override { return m_errorString; }
|
||||
|
||||
QString name() const override { return tr("Web Search"); }
|
||||
QString description() const override { return tr("Search the web"); }
|
||||
QString function() const override { return "web_search"; }
|
||||
ToolEnums::PrivacyScope privacyScope() const override { return ToolEnums::PrivacyScope::None; }
|
||||
QJsonObject paramSchema() const override;
|
||||
QJsonObject exampleParams() const override;
|
||||
bool isBuiltin() const override { return true; }
|
||||
ToolEnums::UsageMode usageMode() const override;
|
||||
ToolEnums::ConfirmationMode confirmationMode() const override;
|
||||
bool excerpts() const override { return true; }
|
||||
|
||||
private:
|
||||
ToolEnums::Error m_error;
|
||||
QString m_errorString;
|
||||
};
|
||||
|
||||
#endif // BRAVESEARCH_H
|
||||
@@ -59,6 +59,7 @@ void Chat::connectLLM()
|
||||
connect(m_llmodel, &ChatLLM::responseChanged, this, &Chat::handleResponseChanged, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::promptProcessing, this, &Chat::promptProcessing, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::generatingQuestions, this, &Chat::generatingQuestions, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::toolCalled, this, &Chat::toolCalled, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::responseStopped, this, &Chat::responseStopped, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::modelLoadingError, this, &Chat::handleModelLoadingError, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::modelLoadingWarning, this, &Chat::modelLoadingWarning, Qt::QueuedConnection);
|
||||
@@ -67,7 +68,7 @@ void Chat::connectLLM()
|
||||
connect(m_llmodel, &ChatLLM::generatedQuestionFinished, this, &Chat::generatedQuestionFinished, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::reportSpeed, this, &Chat::handleTokenSpeedChanged, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::loadedModelInfoChanged, this, &Chat::loadedModelInfoChanged, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::databaseResultsChanged, this, &Chat::handleDatabaseResultsChanged, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::sourceExcerptsChanged, this, &Chat::handleSourceExcerptsChanged, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::modelInfoChanged, this, &Chat::handleModelInfoChanged, Qt::QueuedConnection);
|
||||
connect(m_llmodel, &ChatLLM::trySwitchContextOfLoadedModelCompleted, this, &Chat::handleTrySwitchContextOfLoadedModelCompleted, Qt::QueuedConnection);
|
||||
|
||||
@@ -115,12 +116,14 @@ void Chat::resetResponseState()
|
||||
if (m_responseInProgress && m_responseState == Chat::LocalDocsRetrieval)
|
||||
return;
|
||||
|
||||
m_sourceExcerpts = QList<SourceExcerpt>();
|
||||
m_generatedQuestions = QList<QString>();
|
||||
emit generatedQuestionsChanged();
|
||||
m_tokenSpeed = QString();
|
||||
emit tokenSpeedChanged();
|
||||
m_responseInProgress = true;
|
||||
m_responseState = m_collections.empty() ? Chat::PromptProcessing : Chat::LocalDocsRetrieval;
|
||||
m_toolDescription = QString();
|
||||
emit responseInProgressChanged();
|
||||
emit responseStateChanged();
|
||||
}
|
||||
@@ -134,7 +137,8 @@ void Chat::prompt(const QString &prompt)
|
||||
void Chat::regenerateResponse()
|
||||
{
|
||||
const int index = m_chatModel->count() - 1;
|
||||
m_chatModel->updateSources(index, QList<ResultInfo>());
|
||||
m_sourceExcerpts = QList<SourceExcerpt>();
|
||||
m_chatModel->updateSources(index, QList<SourceExcerpt>());
|
||||
emit regenerateResponseRequested();
|
||||
}
|
||||
|
||||
@@ -189,8 +193,13 @@ void Chat::handleModelLoadingPercentageChanged(float loadingPercentage)
|
||||
|
||||
void Chat::promptProcessing()
|
||||
{
|
||||
m_responseState = !databaseResults().isEmpty() ? Chat::LocalDocsProcessing : Chat::PromptProcessing;
|
||||
emit responseStateChanged();
|
||||
if (sourceExcerpts().isEmpty())
|
||||
m_responseState = Chat::PromptProcessing;
|
||||
else if (m_responseState == Chat::ToolCalled)
|
||||
m_responseState = Chat::ToolProcessing;
|
||||
else
|
||||
m_responseState = Chat::LocalDocsProcessing;
|
||||
emit responseStateChanged();
|
||||
}
|
||||
|
||||
void Chat::generatingQuestions()
|
||||
@@ -199,6 +208,14 @@ void Chat::generatingQuestions()
|
||||
emit responseStateChanged();
|
||||
}
|
||||
|
||||
void Chat::toolCalled(const QString &description)
|
||||
{
|
||||
m_responseState = Chat::ToolCalled;
|
||||
m_toolDescription = description;
|
||||
emit toolDescriptionChanged();
|
||||
emit responseStateChanged();
|
||||
}
|
||||
|
||||
void Chat::responseStopped(qint64 promptResponseMs)
|
||||
{
|
||||
m_tokenSpeed = QString();
|
||||
@@ -357,11 +374,11 @@ QString Chat::fallbackReason() const
|
||||
return m_llmodel->fallbackReason();
|
||||
}
|
||||
|
||||
void Chat::handleDatabaseResultsChanged(const QList<ResultInfo> &results)
|
||||
void Chat::handleSourceExcerptsChanged(const QList<SourceExcerpt> &sourceExcerpts)
|
||||
{
|
||||
m_databaseResults = results;
|
||||
m_sourceExcerpts = sourceExcerpts;
|
||||
const int index = m_chatModel->count() - 1;
|
||||
m_chatModel->updateSources(index, m_databaseResults);
|
||||
m_chatModel->updateSources(index, m_sourceExcerpts);
|
||||
}
|
||||
|
||||
void Chat::handleModelInfoChanged(const ModelInfo &modelInfo)
|
||||
|
||||
@@ -40,6 +40,7 @@ class Chat : public QObject
|
||||
// 0=no, 1=waiting, 2=working
|
||||
Q_PROPERTY(int trySwitchContextInProgress READ trySwitchContextInProgress NOTIFY trySwitchContextInProgressChanged)
|
||||
Q_PROPERTY(QList<QString> generatedQuestions READ generatedQuestions NOTIFY generatedQuestionsChanged)
|
||||
Q_PROPERTY(QString toolDescription READ toolDescription NOTIFY toolDescriptionChanged)
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("Only creatable from c++!")
|
||||
|
||||
@@ -50,7 +51,9 @@ public:
|
||||
LocalDocsProcessing,
|
||||
PromptProcessing,
|
||||
GeneratingQuestions,
|
||||
ResponseGeneration
|
||||
ResponseGeneration,
|
||||
ToolCalled,
|
||||
ToolProcessing
|
||||
};
|
||||
Q_ENUM(ResponseState)
|
||||
|
||||
@@ -81,9 +84,10 @@ public:
|
||||
Q_INVOKABLE void stopGenerating();
|
||||
Q_INVOKABLE void newPromptResponsePair(const QString &prompt);
|
||||
|
||||
QList<ResultInfo> databaseResults() const { return m_databaseResults; }
|
||||
QList<SourceExcerpt> sourceExcerpts() const { return m_sourceExcerpts; }
|
||||
|
||||
QString response() const;
|
||||
QString toolDescription() const { return m_toolDescription; }
|
||||
bool responseInProgress() const { return m_responseInProgress; }
|
||||
ResponseState responseState() const;
|
||||
ModelInfo modelInfo() const;
|
||||
@@ -158,19 +162,21 @@ Q_SIGNALS:
|
||||
void trySwitchContextInProgressChanged();
|
||||
void loadedModelInfoChanged();
|
||||
void generatedQuestionsChanged();
|
||||
void toolDescriptionChanged();
|
||||
|
||||
private Q_SLOTS:
|
||||
void handleResponseChanged(const QString &response);
|
||||
void handleModelLoadingPercentageChanged(float);
|
||||
void promptProcessing();
|
||||
void generatingQuestions();
|
||||
void toolCalled(const QString &description);
|
||||
void responseStopped(qint64 promptResponseMs);
|
||||
void generatedNameChanged(const QString &name);
|
||||
void generatedQuestionFinished(const QString &question);
|
||||
void handleRestoringFromText();
|
||||
void handleModelLoadingError(const QString &error);
|
||||
void handleTokenSpeedChanged(const QString &tokenSpeed);
|
||||
void handleDatabaseResultsChanged(const QList<ResultInfo> &results);
|
||||
void handleSourceExcerptsChanged(const QList<SourceExcerpt> &sourceExcerpts);
|
||||
void handleModelInfoChanged(const ModelInfo &modelInfo);
|
||||
void handleTrySwitchContextOfLoadedModelCompleted(int value);
|
||||
|
||||
@@ -185,6 +191,7 @@ private:
|
||||
QString m_device;
|
||||
QString m_fallbackReason;
|
||||
QString m_response;
|
||||
QString m_toolDescription;
|
||||
QList<QString> m_collections;
|
||||
QList<QString> m_generatedQuestions;
|
||||
ChatModel *m_chatModel;
|
||||
@@ -192,7 +199,7 @@ private:
|
||||
ResponseState m_responseState;
|
||||
qint64 m_creationDate;
|
||||
ChatLLM *m_llmodel;
|
||||
QList<ResultInfo> m_databaseResults;
|
||||
QList<SourceExcerpt> m_sourceExcerpts;
|
||||
bool m_isServer = false;
|
||||
bool m_shouldDeleteLater = false;
|
||||
float m_modelLoadingPercentage = 0.0f;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include <algorithm>
|
||||
|
||||
#define CHAT_FORMAT_MAGIC 0xF5D553CC
|
||||
#define CHAT_FORMAT_VERSION 9
|
||||
#define CHAT_FORMAT_VERSION 10
|
||||
|
||||
class MyChatListModel: public ChatListModel { };
|
||||
Q_GLOBAL_STATIC(MyChatListModel, chatListModelInstance)
|
||||
@@ -31,7 +31,7 @@ ChatListModel *ChatListModel::globalInstance()
|
||||
ChatListModel::ChatListModel()
|
||||
: QAbstractListModel(nullptr) {
|
||||
|
||||
QCoreApplication::instance()->installEventFilter(this);
|
||||
QCoreApplication::instance()->installEventFilter(this);
|
||||
}
|
||||
|
||||
bool ChatListModel::eventFilter(QObject *obj, QEvent *ev)
|
||||
|
||||
@@ -2,15 +2,19 @@
|
||||
|
||||
#include "chat.h"
|
||||
#include "chatapi.h"
|
||||
#include "localdocs.h"
|
||||
#include "localdocssearch.h"
|
||||
#include "mysettings.h"
|
||||
#include "network.h"
|
||||
#include "tool.h"
|
||||
#include "toolmodel.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QGlobalStatic>
|
||||
#include <QGuiApplication>
|
||||
#include <QIODevice>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QMutex>
|
||||
@@ -33,6 +37,7 @@
|
||||
#include <vector>
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
using namespace ToolEnums;
|
||||
|
||||
//#define DEBUG
|
||||
//#define DEBUG_MODEL_LOADING
|
||||
@@ -113,6 +118,9 @@ ChatLLM::ChatLLM(Chat *parent, bool isServer)
|
||||
, m_reloadingToChangeVariant(false)
|
||||
, m_processedSystemPrompt(false)
|
||||
, m_restoreStateFromText(false)
|
||||
, m_checkToolCall(false)
|
||||
, m_maybeToolCall(false)
|
||||
, m_foundToolCall(false)
|
||||
{
|
||||
moveToThread(&m_llmThread);
|
||||
connect(this, &ChatLLM::shouldBeLoadedChanged, this, &ChatLLM::handleShouldBeLoadedChanged,
|
||||
@@ -123,11 +131,6 @@ ChatLLM::ChatLLM(Chat *parent, bool isServer)
|
||||
connect(&m_llmThread, &QThread::started, this, &ChatLLM::handleThreadStarted);
|
||||
connect(MySettings::globalInstance(), &MySettings::forceMetalChanged, this, &ChatLLM::handleForceMetalChanged);
|
||||
connect(MySettings::globalInstance(), &MySettings::deviceChanged, this, &ChatLLM::handleDeviceChanged);
|
||||
|
||||
// The following are blocking operations and will block the llm thread
|
||||
connect(this, &ChatLLM::requestRetrieveFromDB, LocalDocs::globalInstance()->database(), &Database::retrieveFromDB,
|
||||
Qt::BlockingQueuedConnection);
|
||||
|
||||
m_llmThread.setObjectName(parent->id());
|
||||
m_llmThread.start();
|
||||
}
|
||||
@@ -708,7 +711,28 @@ bool ChatLLM::handleResponse(int32_t token, const std::string &response)
|
||||
m_timer->inc();
|
||||
Q_ASSERT(!response.empty());
|
||||
m_response.append(response);
|
||||
emit responseChanged(QString::fromStdString(remove_leading_whitespace(m_response)));
|
||||
|
||||
// If we're checking for a tool called and the response is equal or exceeds 11 chars
|
||||
// then we check
|
||||
if (m_checkToolCall && m_response.size() >= 11) {
|
||||
if (m_response.starts_with("<tool_call>")) {
|
||||
m_maybeToolCall = true;
|
||||
m_response.erase(0, 11);
|
||||
}
|
||||
m_checkToolCall = false;
|
||||
}
|
||||
|
||||
// Check if we're at the end of tool call and erase the end tag
|
||||
if (m_maybeToolCall && m_response.ends_with("</tool_call>")) {
|
||||
m_foundToolCall = true;
|
||||
m_response.erase(m_response.length() - 12);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we're not checking for tool call and haven't detected one, then send along the response
|
||||
if (!m_checkToolCall && !m_maybeToolCall)
|
||||
emit responseChanged(QString::fromStdString(remove_leading_whitespace(m_response)));
|
||||
|
||||
return !m_stopGenerating;
|
||||
}
|
||||
|
||||
@@ -737,27 +761,109 @@ bool ChatLLM::prompt(const QList<QString> &collectionList, const QString &prompt
|
||||
bool ChatLLM::promptInternal(const QList<QString> &collectionList, const QString &prompt, const QString &promptTemplate,
|
||||
int32_t n_predict, int32_t top_k, float top_p, float min_p, float temp, int32_t n_batch, float repeat_penalty,
|
||||
int32_t repeat_penalty_tokens)
|
||||
{
|
||||
QString toolCallingTemplate = MySettings::globalInstance()->modelToolTemplate(m_modelInfo);
|
||||
Q_ASSERT(toolCallingTemplate.isEmpty() || toolCallingTemplate.contains("%1"));
|
||||
if (toolCallingTemplate.isEmpty() || !toolCallingTemplate.contains("%1"))
|
||||
toolCallingTemplate = u"### Context:\n%1\n\n"_s;
|
||||
|
||||
const bool isToolCallingModel = MySettings::globalInstance()->modelIsToolCalling(m_modelInfo);
|
||||
|
||||
// Iterate over the list of tools and if force usage is set, then we *try* and force usage here
|
||||
QList<QString> toolResponses;
|
||||
qint64 totalTime = 0;
|
||||
bool producedSourceExcerpts = false;
|
||||
const int toolCount = ToolModel::globalInstance()->count();
|
||||
for (int i = 0; i < toolCount; ++i) {
|
||||
Tool *t = ToolModel::globalInstance()->get(i);
|
||||
if (t->usageMode() != UsageMode::ForceUsage)
|
||||
continue;
|
||||
|
||||
// Local docs search is unique. It is the _only_ tool where we try and force usage even if
|
||||
// the model does not support tool calling.
|
||||
if (!isToolCallingModel && t->function() != "localdocs_search")
|
||||
continue;
|
||||
|
||||
// If this is the localdocs tool call, then we perform the search with the entire prompt as
|
||||
// the query
|
||||
if (t->function() == "localdocs_search") {
|
||||
if (collectionList.isEmpty())
|
||||
continue;
|
||||
|
||||
QJsonObject parameters;
|
||||
parameters.insert("collections", QJsonArray::fromStringList(collectionList));
|
||||
parameters.insert("query", prompt);
|
||||
parameters.insert("count", MySettings::globalInstance()->localDocsRetrievalSize());
|
||||
|
||||
// FIXME: Honor the confirmation mode feature
|
||||
const QString response = t->run(parameters, 2000 /*msecs to timeout*/);
|
||||
if (t->error() != Error::NoError) {
|
||||
qWarning() << "ERROR: LocalDocs call produced error:" << t->errorString();
|
||||
continue;
|
||||
}
|
||||
|
||||
QString parseError;
|
||||
QList<SourceExcerpt> localDocsExcerpts = SourceExcerpt::fromJson(response, parseError);
|
||||
if (!parseError.isEmpty()) {
|
||||
qWarning() << "ERROR: Could not parse source excerpts for localdocs response:" << parseError;
|
||||
} else {
|
||||
producedSourceExcerpts = true;
|
||||
emit sourceExcerptsChanged(localDocsExcerpts);
|
||||
}
|
||||
toolResponses << QString(toolCallingTemplate).arg(response);
|
||||
continue;
|
||||
}
|
||||
|
||||
// For all other cases we should have a tool calling model
|
||||
Q_ASSERT(isToolCallingModel);
|
||||
|
||||
// Create the tool calling response as if the model has chosen this particular tool
|
||||
const QString toolCallingResponse = QString("<tool_call>{\"name\": \"%1\", \"parameters\": {\"").arg(t->function());
|
||||
|
||||
// Mimic that the model has already responded like this to trigger our tool calling detection
|
||||
// code and then rely upon it to complete the parameters correctly
|
||||
m_response = toolCallingResponse.toStdString();
|
||||
|
||||
// Insert this response as the tool prompt
|
||||
const QString toolPrompt = QString(promptTemplate).arg(prompt, toolCallingResponse);
|
||||
|
||||
const QString toolCall = completeToolCall(toolPrompt, n_predict, top_k, top_p,
|
||||
min_p, temp, n_batch, repeat_penalty, repeat_penalty_tokens, totalTime);
|
||||
|
||||
// If the tool call is empty, then we failed in our attempt to force usage
|
||||
if (toolCall.isEmpty()) {
|
||||
qWarning() << "WARNING: Attempt to force usage of toolcall" << t->function() << "failed:"
|
||||
<< "model could not complete parameters for" << toolPrompt;
|
||||
continue;
|
||||
}
|
||||
|
||||
QString errorString;
|
||||
const QString response = executeToolCall(toolCall, producedSourceExcerpts, errorString);
|
||||
if (response.isEmpty()) {
|
||||
qWarning() << "WARNING: Attempt to force usage of toolcall" << t->function() << "failed:" << errorString;
|
||||
continue;
|
||||
}
|
||||
|
||||
toolResponses << QString(toolCallingTemplate).arg(response);
|
||||
}
|
||||
|
||||
bool success = promptRecursive({ toolResponses }, prompt, promptTemplate, n_predict, top_k, top_p,
|
||||
min_p, temp, n_batch, repeat_penalty, repeat_penalty_tokens, totalTime, producedSourceExcerpts);
|
||||
Q_ASSERT(success);
|
||||
SuggestionMode mode = MySettings::globalInstance()->suggestionMode();
|
||||
if (mode == SuggestionMode::On || (mode == SuggestionMode::SourceExcerptsOnly && producedSourceExcerpts))
|
||||
generateQuestions(totalTime);
|
||||
else
|
||||
emit responseStopped(totalTime);
|
||||
return success;
|
||||
}
|
||||
|
||||
QString ChatLLM::completeToolCall(const QString &prompt, int32_t n_predict, int32_t top_k, float top_p,
|
||||
float min_p, float temp, int32_t n_batch, float repeat_penalty, int32_t repeat_penalty_tokens,
|
||||
qint64 &totalTime)
|
||||
{
|
||||
if (!isModelLoaded())
|
||||
return false;
|
||||
|
||||
QList<ResultInfo> databaseResults;
|
||||
const int retrievalSize = MySettings::globalInstance()->localDocsRetrievalSize();
|
||||
if (!collectionList.isEmpty()) {
|
||||
emit requestRetrieveFromDB(collectionList, prompt, retrievalSize, &databaseResults); // blocks
|
||||
emit databaseResultsChanged(databaseResults);
|
||||
}
|
||||
|
||||
// Augment the prompt template with the results if any
|
||||
QString docsContext;
|
||||
if (!databaseResults.isEmpty()) {
|
||||
QStringList results;
|
||||
for (const ResultInfo &info : databaseResults)
|
||||
results << u"Collection: %1\nPath: %2\nExcerpt: %3"_s.arg(info.collection, info.path, info.text);
|
||||
|
||||
// FIXME(jared): use a Jinja prompt template instead of hardcoded Alpaca-style localdocs template
|
||||
docsContext = u"### Context:\n%1\n\n"_s.arg(results.join("\n\n"));
|
||||
}
|
||||
return QString();
|
||||
|
||||
int n_threads = MySettings::globalInstance()->threadCount();
|
||||
|
||||
@@ -779,37 +885,188 @@ bool ChatLLM::promptInternal(const QList<QString> &collectionList, const QString
|
||||
printf("%s", qPrintable(prompt));
|
||||
fflush(stdout);
|
||||
#endif
|
||||
QElapsedTimer totalTime;
|
||||
totalTime.start();
|
||||
|
||||
QElapsedTimer elapsedTimer;
|
||||
elapsedTimer.start();
|
||||
m_timer->start();
|
||||
if (!docsContext.isEmpty()) {
|
||||
auto old_n_predict = std::exchange(m_ctx.n_predict, 0); // decode localdocs context without a response
|
||||
m_llModelInfo.model->prompt(docsContext.toStdString(), "%1", promptFunc, responseFunc,
|
||||
|
||||
m_checkToolCall = true;
|
||||
|
||||
// We pass in the prompt as the completed template as we're mimicking that the respone has already
|
||||
// started
|
||||
LLModel::PromptContext ctx = m_ctx;
|
||||
m_llModelInfo.model->prompt(prompt.toStdString(), "%1", promptFunc, responseFunc,
|
||||
/*allowContextShift*/ false, ctx);
|
||||
|
||||
// After the response has been handled reset this state
|
||||
m_checkToolCall = false;
|
||||
m_maybeToolCall = false;
|
||||
|
||||
m_timer->stop();
|
||||
totalTime = elapsedTimer.elapsed();
|
||||
|
||||
const QString toolCall = QString::fromStdString(trim_whitespace(m_response));
|
||||
m_promptResponseTokens = 0;
|
||||
m_promptTokens = 0;
|
||||
m_response = std::string();
|
||||
|
||||
if (!m_foundToolCall)
|
||||
return QString();
|
||||
|
||||
m_foundToolCall = false;
|
||||
return toolCall;
|
||||
}
|
||||
|
||||
QString ChatLLM::executeToolCall(const QString &toolCall, bool &producedSourceExcerpts, QString &errorString)
|
||||
{
|
||||
const QString toolTemplate = MySettings::globalInstance()->modelToolTemplate(m_modelInfo);
|
||||
if (toolTemplate.isEmpty()) {
|
||||
errorString = QString("ERROR: No valid tool template for this model %1").arg(toolCall);
|
||||
return QString();
|
||||
}
|
||||
|
||||
QJsonParseError err;
|
||||
const QJsonDocument toolCallDoc = QJsonDocument::fromJson(toolCall.toUtf8(), &err);
|
||||
|
||||
if (toolCallDoc.isNull() || err.error != QJsonParseError::NoError || !toolCallDoc.isObject()) {
|
||||
errorString = QString("ERROR: The tool call had null or invalid json %1").arg(toolCall);
|
||||
return QString();
|
||||
}
|
||||
|
||||
QJsonObject rootObject = toolCallDoc.object();
|
||||
if (!rootObject.contains("name") || !rootObject.contains("parameters")) {
|
||||
errorString = QString("ERROR: The tool call did not have required name and argument objects %1").arg(toolCall);
|
||||
return QString();
|
||||
}
|
||||
|
||||
const QString tool = toolCallDoc["name"].toString();
|
||||
const QJsonObject args = toolCallDoc["parameters"].toObject();
|
||||
|
||||
Tool *toolInstance = ToolModel::globalInstance()->get(tool);
|
||||
if (!toolInstance) {
|
||||
errorString = QString("ERROR: Could not find the tool for %1").arg(toolCall);
|
||||
return QString();
|
||||
}
|
||||
|
||||
// FIXME: Honor the confirmation mode feature
|
||||
// Inform the chat that we're executing a tool call
|
||||
emit toolCalled(toolInstance->name().toLower());
|
||||
|
||||
const QString response = toolInstance->run(args, 2000 /*msecs to timeout*/);
|
||||
if (toolInstance->error() != Error::NoError) {
|
||||
errorString = QString("ERROR: Tool call produced error: %1").arg(toolInstance->errorString());
|
||||
return QString();
|
||||
}
|
||||
|
||||
// If the tool supports excerpts then try to parse them here, but it isn't strictly an error
|
||||
// but rather a warning
|
||||
if (toolInstance->excerpts()) {
|
||||
QString parseError;
|
||||
QList<SourceExcerpt> sourceExcerpts = SourceExcerpt::fromJson(response, parseError);
|
||||
if (!parseError.isEmpty()) {
|
||||
qWarning() << "WARNING: Could not parse source excerpts for response:" << parseError;
|
||||
} else if (!sourceExcerpts.isEmpty()) {
|
||||
producedSourceExcerpts = true;
|
||||
emit sourceExcerptsChanged(sourceExcerpts);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
bool ChatLLM::promptRecursive(const QList<QString> &toolResponses, const QString &prompt,
|
||||
const QString &promptTemplate, int32_t n_predict, int32_t top_k, float top_p, float min_p, float temp,
|
||||
int32_t n_batch, float repeat_penalty, int32_t repeat_penalty_tokens, qint64 &totalTime, bool &producedSourceExcerpts, bool isRecursiveCall)
|
||||
{
|
||||
if (!isModelLoaded())
|
||||
return false;
|
||||
|
||||
int n_threads = MySettings::globalInstance()->threadCount();
|
||||
|
||||
m_stopGenerating = false;
|
||||
auto promptFunc = std::bind(&ChatLLM::handlePrompt, this, std::placeholders::_1);
|
||||
auto responseFunc = std::bind(&ChatLLM::handleResponse, this, std::placeholders::_1,
|
||||
std::placeholders::_2);
|
||||
emit promptProcessing();
|
||||
m_ctx.n_predict = n_predict;
|
||||
m_ctx.top_k = top_k;
|
||||
m_ctx.top_p = top_p;
|
||||
m_ctx.min_p = min_p;
|
||||
m_ctx.temp = temp;
|
||||
m_ctx.n_batch = n_batch;
|
||||
m_ctx.repeat_penalty = repeat_penalty;
|
||||
m_ctx.repeat_last_n = repeat_penalty_tokens;
|
||||
m_llModelInfo.model->setThreadCount(n_threads);
|
||||
#if defined(DEBUG)
|
||||
printf("%s", qPrintable(prompt));
|
||||
fflush(stdout);
|
||||
#endif
|
||||
|
||||
QElapsedTimer elapsedTimer;
|
||||
elapsedTimer.start();
|
||||
m_timer->start();
|
||||
|
||||
// The list of possible additional responses that come from previous usage of tool calls
|
||||
for (const QString &context : toolResponses) {
|
||||
auto old_n_predict = std::exchange(m_ctx.n_predict, 0); // decode context without a response
|
||||
m_llModelInfo.model->prompt(context.toStdString(), "%1", promptFunc, responseFunc,
|
||||
/*allowContextShift*/ true, m_ctx);
|
||||
m_ctx.n_predict = old_n_predict; // now we are ready for a response
|
||||
}
|
||||
|
||||
// We can't handle recursive tool calls right now due to the possibility of the model causing
|
||||
// infinite recursion through repeated tool calls
|
||||
m_checkToolCall = !isRecursiveCall;
|
||||
|
||||
m_llModelInfo.model->prompt(prompt.toStdString(), promptTemplate.toStdString(), promptFunc, responseFunc,
|
||||
/*allowContextShift*/ true, m_ctx);
|
||||
|
||||
// After the response has been handled reset this state
|
||||
m_checkToolCall = false;
|
||||
m_maybeToolCall = false;
|
||||
|
||||
#if defined(DEBUG)
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
#endif
|
||||
m_timer->stop();
|
||||
qint64 elapsed = totalTime.elapsed();
|
||||
totalTime = elapsedTimer.elapsed();
|
||||
std::string trimmed = trim_whitespace(m_response);
|
||||
if (trimmed != m_response) {
|
||||
m_response = trimmed;
|
||||
emit responseChanged(QString::fromStdString(m_response));
|
||||
|
||||
// If we found a tool call, then deal with it
|
||||
if (m_foundToolCall) {
|
||||
m_foundToolCall = false;
|
||||
|
||||
QString errorString;
|
||||
const QString toolCall = QString::fromStdString(trimmed);
|
||||
const QString toolResponse = executeToolCall(toolCall, producedSourceExcerpts, errorString);
|
||||
if (toolResponse.isEmpty()) {
|
||||
// FIXME: Need to surface errors to the UI
|
||||
// Restore the strings that we excluded previously when detecting the tool call
|
||||
qWarning() << errorString;
|
||||
m_response = "<tool_call>" + toolCall.toStdString() + "</tool_call>";
|
||||
emit responseChanged(QString::fromStdString(m_response));
|
||||
emit responseStopped(totalTime);
|
||||
m_pristineLoadedState = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset the state now that we've had a successful tool call response
|
||||
m_promptResponseTokens = 0;
|
||||
m_promptTokens = 0;
|
||||
m_response = std::string();
|
||||
|
||||
// This is a recursive call but flag is checked above to arrest infinite recursive tool calls
|
||||
return promptRecursive({ toolResponse }, prompt, promptTemplate,
|
||||
n_predict, top_k, top_p, min_p, temp, n_batch, repeat_penalty, repeat_penalty_tokens, totalTime,
|
||||
producedSourceExcerpts, true /*isRecursiveCall*/);
|
||||
} else {
|
||||
if (trimmed != m_response) {
|
||||
m_response = trimmed;
|
||||
emit responseChanged(QString::fromStdString(m_response));
|
||||
}
|
||||
m_pristineLoadedState = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
SuggestionMode mode = MySettings::globalInstance()->suggestionMode();
|
||||
if (mode == SuggestionMode::On || (!databaseResults.isEmpty() && mode == SuggestionMode::LocalDocsOnly))
|
||||
generateQuestions(elapsed);
|
||||
else
|
||||
emit responseStopped(elapsed);
|
||||
|
||||
m_pristineLoadedState = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChatLLM::setShouldBeLoaded(bool b)
|
||||
@@ -1209,7 +1466,15 @@ void ChatLLM::processSystemPrompt()
|
||||
if (!isModelLoaded() || m_processedSystemPrompt || m_restoreStateFromText || m_isServer)
|
||||
return;
|
||||
|
||||
const std::string systemPrompt = MySettings::globalInstance()->modelSystemPrompt(m_modelInfo).toStdString();
|
||||
QString error;
|
||||
const std::string systemPrompt = MySettings::globalInstance()->modelSystemPrompt(m_modelInfo, error).toStdString();
|
||||
|
||||
// The GUI should not allow setting an improper template, but it is always possible someone hand
|
||||
// edits the settings file to produce an improper one.
|
||||
Q_ASSERT(error.isEmpty());
|
||||
if (!error.isEmpty())
|
||||
qWarning() << "ERROR: Could not parse system prompt template:" << error;
|
||||
|
||||
if (QString::fromStdString(systemPrompt).trimmed().isEmpty()) {
|
||||
m_processedSystemPrompt = true;
|
||||
return;
|
||||
|
||||
@@ -180,6 +180,7 @@ Q_SIGNALS:
|
||||
void responseChanged(const QString &response);
|
||||
void promptProcessing();
|
||||
void generatingQuestions();
|
||||
void toolCalled(const QString &description);
|
||||
void responseStopped(qint64 promptResponseMs);
|
||||
void generatedNameChanged(const QString &name);
|
||||
void generatedQuestionFinished(const QString &generatedQuestion);
|
||||
@@ -188,14 +189,14 @@ Q_SIGNALS:
|
||||
void shouldBeLoadedChanged();
|
||||
void trySwitchContextRequested(const ModelInfo &modelInfo);
|
||||
void trySwitchContextOfLoadedModelCompleted(int value);
|
||||
void requestRetrieveFromDB(const QList<QString> &collections, const QString &text, int retrievalSize, QList<ResultInfo> *results);
|
||||
void reportSpeed(const QString &speed);
|
||||
void reportDevice(const QString &device);
|
||||
void reportFallbackReason(const QString &fallbackReason);
|
||||
void databaseResultsChanged(const QList<ResultInfo>&);
|
||||
void sourceExcerptsChanged(const QList<SourceExcerpt>&);
|
||||
void modelInfoChanged(const ModelInfo &modelInfo);
|
||||
|
||||
protected:
|
||||
// FIXME: This is only available because of server which sucks
|
||||
bool promptInternal(const QList<QString> &collectionList, const QString &prompt, const QString &promptTemplate,
|
||||
int32_t n_predict, int32_t top_k, float top_p, float min_p, float temp, int32_t n_batch, float repeat_penalty,
|
||||
int32_t repeat_penalty_tokens);
|
||||
@@ -218,6 +219,13 @@ protected:
|
||||
quint32 m_promptResponseTokens;
|
||||
|
||||
private:
|
||||
QString completeToolCall(const QString &promptTemplate, int32_t n_predict, int32_t top_k, float top_p,
|
||||
float min_p, float temp, int32_t n_batch, float repeat_penalty, int32_t repeat_penalty_tokens,
|
||||
qint64 &totalTime);
|
||||
QString executeToolCall(const QString &toolCall, bool &producedSourceExcerpts, QString &errorString);
|
||||
bool promptRecursive(const QList<QString> &toolContexts, const QString &prompt, const QString &promptTemplate,
|
||||
int32_t n_predict, int32_t top_k, float top_p, float min_p, float temp, int32_t n_batch, float repeat_penalty,
|
||||
int32_t repeat_penalty_tokens, qint64 &totalTime, bool &producedSourceExcerpts, bool isRecursiveCall = false);
|
||||
bool loadNewModel(const ModelInfo &modelInfo, QVariantMap &modelLoadProps);
|
||||
|
||||
std::string m_response;
|
||||
@@ -239,11 +247,15 @@ private:
|
||||
bool m_reloadingToChangeVariant;
|
||||
bool m_processedSystemPrompt;
|
||||
bool m_restoreStateFromText;
|
||||
bool m_checkToolCall;
|
||||
bool m_maybeToolCall;
|
||||
bool m_foundToolCall;
|
||||
// m_pristineLoadedState is set if saveSate is unnecessary, either because:
|
||||
// - an unload was queued during LLModel::restoreState()
|
||||
// - the chat will be restored from text and hasn't been interacted with yet
|
||||
bool m_pristineLoadedState = false;
|
||||
QVector<QPair<QString, QString>> m_stateFromText;
|
||||
QNetworkAccessManager m_networkManager; // FIXME REMOVE
|
||||
};
|
||||
|
||||
#endif // CHATLLM_H
|
||||
|
||||
@@ -28,8 +28,7 @@ struct ChatItem
|
||||
Q_PROPERTY(bool stopped MEMBER stopped)
|
||||
Q_PROPERTY(bool thumbsUpState MEMBER thumbsUpState)
|
||||
Q_PROPERTY(bool thumbsDownState MEMBER thumbsDownState)
|
||||
Q_PROPERTY(QList<ResultInfo> sources MEMBER sources)
|
||||
Q_PROPERTY(QList<ResultInfo> consolidatedSources MEMBER consolidatedSources)
|
||||
Q_PROPERTY(QList<SourceExcerpt> sources MEMBER sources)
|
||||
|
||||
public:
|
||||
// TODO: Maybe we should include the model name here as well as timestamp?
|
||||
@@ -38,8 +37,7 @@ public:
|
||||
QString value;
|
||||
QString prompt;
|
||||
QString newResponse;
|
||||
QList<ResultInfo> sources;
|
||||
QList<ResultInfo> consolidatedSources;
|
||||
QList<SourceExcerpt> sources;
|
||||
bool currentResponse = false;
|
||||
bool stopped = false;
|
||||
bool thumbsUpState = false;
|
||||
@@ -65,8 +63,7 @@ public:
|
||||
StoppedRole,
|
||||
ThumbsUpStateRole,
|
||||
ThumbsDownStateRole,
|
||||
SourcesRole,
|
||||
ConsolidatedSourcesRole
|
||||
SourcesRole
|
||||
};
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
@@ -102,8 +99,6 @@ public:
|
||||
return item.thumbsDownState;
|
||||
case SourcesRole:
|
||||
return QVariant::fromValue(item.sources);
|
||||
case ConsolidatedSourcesRole:
|
||||
return QVariant::fromValue(item.consolidatedSources);
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
@@ -122,7 +117,6 @@ public:
|
||||
roles[ThumbsUpStateRole] = "thumbsUpState";
|
||||
roles[ThumbsDownStateRole] = "thumbsDownState";
|
||||
roles[SourcesRole] = "sources";
|
||||
roles[ConsolidatedSourcesRole] = "consolidatedSources";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -200,28 +194,17 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
QList<ResultInfo> consolidateSources(const QList<ResultInfo> &sources) {
|
||||
QMap<QString, ResultInfo> groupedData;
|
||||
for (const ResultInfo &info : sources) {
|
||||
if (groupedData.contains(info.file)) {
|
||||
groupedData[info.file].text += "\n---\n" + info.text;
|
||||
} else {
|
||||
groupedData[info.file] = info;
|
||||
}
|
||||
}
|
||||
QList<ResultInfo> consolidatedSources = groupedData.values();
|
||||
return consolidatedSources;
|
||||
}
|
||||
|
||||
Q_INVOKABLE void updateSources(int index, const QList<ResultInfo> &sources)
|
||||
Q_INVOKABLE void updateSources(int index, const QList<SourceExcerpt> &sources)
|
||||
{
|
||||
if (index < 0 || index >= m_chatItems.size()) return;
|
||||
|
||||
ChatItem &item = m_chatItems[index];
|
||||
item.sources = sources;
|
||||
item.consolidatedSources = consolidateSources(sources);
|
||||
if (sources.isEmpty()) {
|
||||
item.sources.clear();
|
||||
} else {
|
||||
item.sources << sources;
|
||||
}
|
||||
emit dataChanged(createIndex(index, 0), createIndex(index, 0), {SourcesRole});
|
||||
emit dataChanged(createIndex(index, 0), createIndex(index, 0), {ConsolidatedSourcesRole});
|
||||
}
|
||||
|
||||
Q_INVOKABLE void updateThumbsUpState(int index, bool b)
|
||||
@@ -272,57 +255,7 @@ public:
|
||||
stream << c.stopped;
|
||||
stream << c.thumbsUpState;
|
||||
stream << c.thumbsDownState;
|
||||
if (version > 7) {
|
||||
stream << c.sources.size();
|
||||
for (const ResultInfo &info : c.sources) {
|
||||
Q_ASSERT(!info.file.isEmpty());
|
||||
stream << info.collection;
|
||||
stream << info.path;
|
||||
stream << info.file;
|
||||
stream << info.title;
|
||||
stream << info.author;
|
||||
stream << info.date;
|
||||
stream << info.text;
|
||||
stream << info.page;
|
||||
stream << info.from;
|
||||
stream << info.to;
|
||||
}
|
||||
} else if (version > 2) {
|
||||
QList<QString> references;
|
||||
QList<QString> referencesContext;
|
||||
int validReferenceNumber = 1;
|
||||
for (const ResultInfo &info : c.sources) {
|
||||
if (info.file.isEmpty())
|
||||
continue;
|
||||
|
||||
QString reference;
|
||||
{
|
||||
QTextStream stream(&reference);
|
||||
stream << (validReferenceNumber++) << ". ";
|
||||
if (!info.title.isEmpty())
|
||||
stream << "\"" << info.title << "\". ";
|
||||
if (!info.author.isEmpty())
|
||||
stream << "By " << info.author << ". ";
|
||||
if (!info.date.isEmpty())
|
||||
stream << "Date: " << info.date << ". ";
|
||||
stream << "In " << info.file << ". ";
|
||||
if (info.page != -1)
|
||||
stream << "Page " << info.page << ". ";
|
||||
if (info.from != -1) {
|
||||
stream << "Lines " << info.from;
|
||||
if (info.to != -1)
|
||||
stream << "-" << info.to;
|
||||
stream << ". ";
|
||||
}
|
||||
stream << "[Context](context://" << validReferenceNumber - 1 << ")";
|
||||
}
|
||||
references.append(reference);
|
||||
referencesContext.append(info.text);
|
||||
}
|
||||
|
||||
stream << references.join("\n");
|
||||
stream << referencesContext;
|
||||
}
|
||||
stream << SourceExcerpt::toJson(c.sources);
|
||||
}
|
||||
return stream.status() == QDataStream::Ok;
|
||||
}
|
||||
@@ -342,34 +275,43 @@ public:
|
||||
stream >> c.stopped;
|
||||
stream >> c.thumbsUpState;
|
||||
stream >> c.thumbsDownState;
|
||||
if (version > 7) {
|
||||
if (version > 9) {
|
||||
QList<SourceExcerpt> sources;
|
||||
QString json;
|
||||
stream >> json;
|
||||
QString errorString;
|
||||
sources = SourceExcerpt::fromJson(json, errorString);
|
||||
Q_ASSERT(errorString.isEmpty());
|
||||
c.sources = sources;
|
||||
} else if (version > 7) {
|
||||
qsizetype count;
|
||||
stream >> count;
|
||||
QList<ResultInfo> sources;
|
||||
QList<SourceExcerpt> sources;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
ResultInfo info;
|
||||
stream >> info.collection;
|
||||
stream >> info.path;
|
||||
stream >> info.file;
|
||||
stream >> info.title;
|
||||
stream >> info.author;
|
||||
stream >> info.date;
|
||||
stream >> info.text;
|
||||
stream >> info.page;
|
||||
stream >> info.from;
|
||||
stream >> info.to;
|
||||
sources.append(info);
|
||||
SourceExcerpt source;
|
||||
stream >> source.collection;
|
||||
stream >> source.path;
|
||||
stream >> source.file;
|
||||
stream >> source.title;
|
||||
stream >> source.author;
|
||||
stream >> source.date;
|
||||
Excerpt excerpt;
|
||||
stream >> excerpt.text;
|
||||
stream >> excerpt.page;
|
||||
stream >> excerpt.from;
|
||||
stream >> excerpt.to;
|
||||
source.excerpts = QList{ excerpt };
|
||||
sources.append(source);
|
||||
}
|
||||
c.sources = sources;
|
||||
c.consolidatedSources = consolidateSources(sources);
|
||||
}else if (version > 2) {
|
||||
} else if (version > 2) {
|
||||
QString references;
|
||||
QList<QString> referencesContext;
|
||||
stream >> references;
|
||||
stream >> referencesContext;
|
||||
|
||||
if (!references.isEmpty()) {
|
||||
QList<ResultInfo> sources;
|
||||
QList<SourceExcerpt> sources;
|
||||
QList<QString> referenceList = references.split("\n");
|
||||
|
||||
// Ignore empty lines and those that begin with "---" which is no longer used
|
||||
@@ -384,7 +326,8 @@ public:
|
||||
for (int j = 0; j < referenceList.size(); ++j) {
|
||||
QString reference = referenceList[j];
|
||||
QString context = referencesContext[j];
|
||||
ResultInfo info;
|
||||
SourceExcerpt source;
|
||||
Excerpt excerpt;
|
||||
QTextStream refStream(&reference);
|
||||
QString dummy;
|
||||
int validReferenceNumber;
|
||||
@@ -393,28 +336,28 @@ public:
|
||||
if (reference.contains("\"")) {
|
||||
int startIndex = reference.indexOf('"') + 1;
|
||||
int endIndex = reference.indexOf('"', startIndex);
|
||||
info.title = reference.mid(startIndex, endIndex - startIndex);
|
||||
source.title = reference.mid(startIndex, endIndex - startIndex);
|
||||
}
|
||||
|
||||
// Extract author (after "By " and before the next period)
|
||||
if (reference.contains("By ")) {
|
||||
int startIndex = reference.indexOf("By ") + 3;
|
||||
int endIndex = reference.indexOf('.', startIndex);
|
||||
info.author = reference.mid(startIndex, endIndex - startIndex).trimmed();
|
||||
source.author = reference.mid(startIndex, endIndex - startIndex).trimmed();
|
||||
}
|
||||
|
||||
// Extract date (after "Date: " and before the next period)
|
||||
if (reference.contains("Date: ")) {
|
||||
int startIndex = reference.indexOf("Date: ") + 6;
|
||||
int endIndex = reference.indexOf('.', startIndex);
|
||||
info.date = reference.mid(startIndex, endIndex - startIndex).trimmed();
|
||||
source.date = reference.mid(startIndex, endIndex - startIndex).trimmed();
|
||||
}
|
||||
|
||||
// Extract file name (after "In " and before the "[Context]")
|
||||
if (reference.contains("In ") && reference.contains(". [Context]")) {
|
||||
int startIndex = reference.indexOf("In ") + 3;
|
||||
int endIndex = reference.indexOf(". [Context]", startIndex);
|
||||
info.file = reference.mid(startIndex, endIndex - startIndex).trimmed();
|
||||
source.file = reference.mid(startIndex, endIndex - startIndex).trimmed();
|
||||
}
|
||||
|
||||
// Extract page number (after "Page " and before the next space)
|
||||
@@ -422,7 +365,7 @@ public:
|
||||
int startIndex = reference.indexOf("Page ") + 5;
|
||||
int endIndex = reference.indexOf(' ', startIndex);
|
||||
if (endIndex == -1) endIndex = reference.length();
|
||||
info.page = reference.mid(startIndex, endIndex - startIndex).toInt();
|
||||
excerpt.page = reference.mid(startIndex, endIndex - startIndex).toInt();
|
||||
}
|
||||
|
||||
// Extract lines (after "Lines " and before the next space or hyphen)
|
||||
@@ -432,18 +375,18 @@ public:
|
||||
if (endIndex == -1) endIndex = reference.length();
|
||||
int hyphenIndex = reference.indexOf('-', startIndex);
|
||||
if (hyphenIndex != -1 && hyphenIndex < endIndex) {
|
||||
info.from = reference.mid(startIndex, hyphenIndex - startIndex).toInt();
|
||||
info.to = reference.mid(hyphenIndex + 1, endIndex - hyphenIndex - 1).toInt();
|
||||
excerpt.from = reference.mid(startIndex, hyphenIndex - startIndex).toInt();
|
||||
excerpt.to = reference.mid(hyphenIndex + 1, endIndex - hyphenIndex - 1).toInt();
|
||||
} else {
|
||||
info.from = reference.mid(startIndex, endIndex - startIndex).toInt();
|
||||
excerpt.from = reference.mid(startIndex, endIndex - startIndex).toInt();
|
||||
}
|
||||
}
|
||||
info.text = context;
|
||||
sources.append(info);
|
||||
excerpt.text = context;
|
||||
source.excerpts = QList{ excerpt };
|
||||
sources.append(source);
|
||||
}
|
||||
|
||||
c.sources = sources;
|
||||
c.consolidatedSources = consolidateSources(sources);
|
||||
}
|
||||
}
|
||||
beginInsertRows(QModelIndex(), m_chatItems.size(), m_chatItems.size());
|
||||
|
||||
@@ -1938,7 +1938,7 @@ QList<int> Database::searchEmbeddings(const std::vector<float> &query, const QLi
|
||||
}
|
||||
|
||||
void Database::retrieveFromDB(const QList<QString> &collections, const QString &text, int retrievalSize,
|
||||
QList<ResultInfo> *results)
|
||||
QString &jsonResult)
|
||||
{
|
||||
#if defined(DEBUG)
|
||||
qDebug() << "retrieveFromDB" << collections << text << retrievalSize;
|
||||
@@ -1960,37 +1960,49 @@ void Database::retrieveFromDB(const QList<QString> &collections, const QString &
|
||||
return;
|
||||
}
|
||||
|
||||
QMap<QString, QJsonObject> results;
|
||||
while (q.next()) {
|
||||
#if defined(DEBUG)
|
||||
const int rowid = q.value(0).toInt();
|
||||
#endif
|
||||
const QString document_path = q.value(2).toString();
|
||||
const QString chunk_text = q.value(3).toString();
|
||||
const QString date = QDateTime::fromMSecsSinceEpoch(q.value(1).toLongLong()).toString("yyyy, MMMM dd");
|
||||
const QString file = q.value(4).toString();
|
||||
const QString title = q.value(5).toString();
|
||||
const QString author = q.value(6).toString();
|
||||
const int page = q.value(7).toInt();
|
||||
const int from = q.value(8).toInt();
|
||||
const int to = q.value(9).toInt();
|
||||
const QString collectionName = q.value(10).toString();
|
||||
ResultInfo info;
|
||||
info.collection = collectionName;
|
||||
info.path = document_path;
|
||||
info.file = file;
|
||||
info.title = title;
|
||||
info.author = author;
|
||||
info.date = date;
|
||||
info.text = chunk_text;
|
||||
info.page = page;
|
||||
info.from = from;
|
||||
info.to = to;
|
||||
results->append(info);
|
||||
QJsonObject resultObject = results.value(file);
|
||||
resultObject.insert("file", file);
|
||||
resultObject.insert("path", q.value(2).toString());
|
||||
resultObject.insert("date", QDateTime::fromMSecsSinceEpoch(q.value(1).toLongLong()).toString("yyyy, MMMM dd"));
|
||||
resultObject.insert("title", q.value(5).toString());
|
||||
resultObject.insert("author", q.value(6).toString());
|
||||
resultObject.insert("collection", q.value(10).toString());
|
||||
|
||||
QJsonArray excerpts;
|
||||
if (resultObject.contains("excerpts"))
|
||||
excerpts = resultObject["excerpts"].toArray();
|
||||
|
||||
QJsonObject excerptObject;
|
||||
excerptObject.insert("text", q.value(3).toString());
|
||||
excerptObject.insert("page", q.value(7).toInt());
|
||||
excerptObject.insert("from", q.value(8).toInt());
|
||||
excerptObject.insert("to", q.value(9).toInt());
|
||||
excerpts.append(excerptObject);
|
||||
resultObject.insert("excerpts", excerpts);
|
||||
results.insert(file, resultObject);
|
||||
|
||||
#if defined(DEBUG)
|
||||
qDebug() << "retrieve rowid:" << rowid
|
||||
<< "chunk_text:" << chunk_text;
|
||||
#endif
|
||||
}
|
||||
|
||||
QJsonArray resultsArray;
|
||||
QList<QJsonObject> resultsList = results.values();
|
||||
for (const QJsonObject &result : resultsList)
|
||||
resultsArray.append(QJsonValue(result));
|
||||
|
||||
QJsonObject response;
|
||||
response.insert("results", resultsArray);
|
||||
QJsonDocument document(response);
|
||||
// qDebug().noquote() << document.toJson(QJsonDocument::Indented);
|
||||
jsonResult = document.toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
// FIXME This is very slow and non-interruptible and when we close the application and we're
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define DATABASE_H
|
||||
|
||||
#include "embllm.h" // IWYU pragma: keep
|
||||
#include "sourceexcerpt.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QFileInfo>
|
||||
@@ -49,64 +50,6 @@ struct DocumentInfo
|
||||
}
|
||||
};
|
||||
|
||||
struct ResultInfo {
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QString collection MEMBER collection)
|
||||
Q_PROPERTY(QString path MEMBER path)
|
||||
Q_PROPERTY(QString file MEMBER file)
|
||||
Q_PROPERTY(QString title MEMBER title)
|
||||
Q_PROPERTY(QString author MEMBER author)
|
||||
Q_PROPERTY(QString date MEMBER date)
|
||||
Q_PROPERTY(QString text MEMBER text)
|
||||
Q_PROPERTY(int page MEMBER page)
|
||||
Q_PROPERTY(int from MEMBER from)
|
||||
Q_PROPERTY(int to MEMBER to)
|
||||
Q_PROPERTY(QString fileUri READ fileUri STORED false)
|
||||
|
||||
public:
|
||||
QString collection; // [Required] The name of the collection
|
||||
QString path; // [Required] The full path
|
||||
QString file; // [Required] The name of the file, but not the full path
|
||||
QString title; // [Optional] The title of the document
|
||||
QString author; // [Optional] The author of the document
|
||||
QString date; // [Required] The creation or the last modification date whichever is latest
|
||||
QString text; // [Required] The text actually used in the augmented context
|
||||
int page = -1; // [Optional] The page where the text was found
|
||||
int from = -1; // [Optional] The line number where the text begins
|
||||
int to = -1; // [Optional] The line number where the text ends
|
||||
|
||||
QString fileUri() const {
|
||||
// QUrl reserved chars that are not UNSAFE_PATH according to glib/gconvert.c
|
||||
static const QByteArray s_exclude = "!$&'()*+,/:=@~"_ba;
|
||||
|
||||
Q_ASSERT(!QFileInfo(path).isRelative());
|
||||
#ifdef Q_OS_WINDOWS
|
||||
Q_ASSERT(!path.contains('\\')); // Qt normally uses forward slash as path separator
|
||||
#endif
|
||||
|
||||
auto escaped = QString::fromUtf8(QUrl::toPercentEncoding(path, s_exclude));
|
||||
if (escaped.front() != '/')
|
||||
escaped = '/' + escaped;
|
||||
return u"file://"_s + escaped;
|
||||
}
|
||||
|
||||
bool operator==(const ResultInfo &other) const {
|
||||
return file == other.file &&
|
||||
title == other.title &&
|
||||
author == other.author &&
|
||||
date == other.date &&
|
||||
text == other.text &&
|
||||
page == other.page &&
|
||||
from == other.from &&
|
||||
to == other.to;
|
||||
}
|
||||
bool operator!=(const ResultInfo &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(ResultInfo)
|
||||
|
||||
struct CollectionItem {
|
||||
// -- Fields persisted to database --
|
||||
|
||||
@@ -158,7 +101,7 @@ public Q_SLOTS:
|
||||
void forceRebuildFolder(const QString &path);
|
||||
bool addFolder(const QString &collection, const QString &path, const QString &embedding_model);
|
||||
void removeFolder(const QString &collection, const QString &path);
|
||||
void retrieveFromDB(const QList<QString> &collections, const QString &text, int retrievalSize, QList<ResultInfo> *results);
|
||||
void retrieveFromDB(const QList<QString> &collections, const QString &text, int retrievalSize, QString &jsonResult);
|
||||
void changeChunkSize(int chunkSize);
|
||||
void changeFileExtensions(const QStringList &extensions);
|
||||
|
||||
@@ -225,7 +168,6 @@ private:
|
||||
QStringList m_scannedFileExtensions;
|
||||
QTimer *m_scanTimer;
|
||||
QMap<int, QQueue<DocumentInfo>> m_docsToScan;
|
||||
QList<ResultInfo> m_retrieve;
|
||||
QThread m_dbThread;
|
||||
QFileSystemWatcher *m_watcher;
|
||||
QSet<QString> m_watchedPaths;
|
||||
|
||||
103
gpt4all-chat/localdocssearch.cpp
Normal file
103
gpt4all-chat/localdocssearch.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "localdocssearch.h"
|
||||
#include "database.h"
|
||||
#include "localdocs.h"
|
||||
#include "mysettings.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QGuiApplication>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QThread>
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
QString LocalDocsSearch::run(const QJsonObject ¶meters, qint64 timeout)
|
||||
{
|
||||
// Reset the error state
|
||||
m_error = ToolEnums::Error::NoError;
|
||||
m_errorString = QString();
|
||||
|
||||
QList<QString> collections;
|
||||
QJsonArray collectionsArray = parameters["collections"].toArray();
|
||||
for (int i = 0; i < collectionsArray.size(); ++i)
|
||||
collections.append(collectionsArray[i].toString());
|
||||
const QString text = parameters["query"].toString();
|
||||
const int count = MySettings::globalInstance()->localDocsRetrievalSize();
|
||||
QThread workerThread;
|
||||
LocalDocsWorker worker;
|
||||
worker.moveToThread(&workerThread);
|
||||
connect(&worker, &LocalDocsWorker::finished, &workerThread, &QThread::quit, Qt::DirectConnection);
|
||||
connect(&workerThread, &QThread::started, [&worker, collections, text, count]() {
|
||||
worker.request(collections, text, count);
|
||||
});
|
||||
workerThread.start();
|
||||
bool timedOut = !workerThread.wait(timeout);
|
||||
if (timedOut) {
|
||||
m_error = ToolEnums::Error::TimeoutError;
|
||||
m_errorString = tr("ERROR: localdocs timeout");
|
||||
}
|
||||
|
||||
workerThread.wait(timeout);
|
||||
workerThread.quit();
|
||||
workerThread.wait();
|
||||
return worker.response();
|
||||
}
|
||||
|
||||
QJsonObject LocalDocsSearch::paramSchema() const
|
||||
{
|
||||
static const QString localParamSchema = R"({
|
||||
"collections": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "The collections to search",
|
||||
"required": true,
|
||||
"modelGenerated": false,
|
||||
"userConfigured": false
|
||||
},
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "The query to search",
|
||||
"required": true
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"description": "The number of excerpts to return",
|
||||
"required": true,
|
||||
"modelGenerated": false
|
||||
}
|
||||
})";
|
||||
|
||||
static const QJsonDocument localJsonDoc = QJsonDocument::fromJson(localParamSchema.toUtf8());
|
||||
Q_ASSERT(!localJsonDoc.isNull() && localJsonDoc.isObject());
|
||||
return localJsonDoc.object();
|
||||
}
|
||||
|
||||
QJsonObject LocalDocsSearch::exampleParams() const
|
||||
{
|
||||
static const QString example = R"({
|
||||
"query": "the 44th president of the United States"
|
||||
})";
|
||||
static const QJsonDocument exampleDoc = QJsonDocument::fromJson(example.toUtf8());
|
||||
Q_ASSERT(!exampleDoc.isNull() && exampleDoc.isObject());
|
||||
return exampleDoc.object();
|
||||
}
|
||||
|
||||
LocalDocsWorker::LocalDocsWorker()
|
||||
: QObject(nullptr)
|
||||
{
|
||||
// The following are blocking operations and will block the calling thread
|
||||
connect(this, &LocalDocsWorker::requestRetrieveFromDB, LocalDocs::globalInstance()->database(),
|
||||
&Database::retrieveFromDB, Qt::BlockingQueuedConnection);
|
||||
}
|
||||
|
||||
void LocalDocsWorker::request(const QList<QString> &collections, const QString &text, int count)
|
||||
{
|
||||
QString jsonResult;
|
||||
emit requestRetrieveFromDB(collections, text, count, jsonResult); // blocks
|
||||
m_response = jsonResult;
|
||||
emit finished();
|
||||
}
|
||||
52
gpt4all-chat/localdocssearch.h
Normal file
52
gpt4all-chat/localdocssearch.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#ifndef LOCALDOCSSEARCH_H
|
||||
#define LOCALDOCSSEARCH_H
|
||||
|
||||
#include "tool.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class LocalDocsWorker : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
LocalDocsWorker();
|
||||
virtual ~LocalDocsWorker() {}
|
||||
|
||||
QString response() const { return m_response; }
|
||||
|
||||
void request(const QList<QString> &collections, const QString &text, int count);
|
||||
|
||||
Q_SIGNALS:
|
||||
void requestRetrieveFromDB(const QList<QString> &collections, const QString &text, int count, QString &jsonResponse);
|
||||
void finished();
|
||||
|
||||
private:
|
||||
QString m_response;
|
||||
};
|
||||
|
||||
class LocalDocsSearch : public Tool {
|
||||
Q_OBJECT
|
||||
public:
|
||||
LocalDocsSearch() : Tool(), m_error(ToolEnums::Error::NoError) {}
|
||||
virtual ~LocalDocsSearch() {}
|
||||
|
||||
QString run(const QJsonObject ¶meters, qint64 timeout = 2000) override;
|
||||
ToolEnums::Error error() const override { return m_error; }
|
||||
QString errorString() const override { return m_errorString; }
|
||||
|
||||
QString name() const override { return tr("LocalDocs Search"); }
|
||||
QString description() const override { return tr("Search the local docs"); }
|
||||
QString function() const override { return "localdocs_search"; }
|
||||
ToolEnums::PrivacyScope privacyScope() const override { return ToolEnums::PrivacyScope::Local; }
|
||||
QJsonObject exampleParams() const override;
|
||||
QJsonObject paramSchema() const override;
|
||||
bool isBuiltin() const override { return true; }
|
||||
ToolEnums::UsageMode usageMode() const override { return ToolEnums::UsageMode::ForceUsage; }
|
||||
bool excerpts() const override { return true; }
|
||||
|
||||
private:
|
||||
ToolEnums::Error m_error;
|
||||
QString m_errorString;
|
||||
};
|
||||
|
||||
#endif // LOCALDOCSSEARCH_H
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "modellist.h"
|
||||
#include "mysettings.h"
|
||||
#include "network.h"
|
||||
#include "toolmodel.h"
|
||||
|
||||
#include "../gpt4all-backend/llmodel.h"
|
||||
|
||||
@@ -67,6 +68,8 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterSingletonInstance("download", 1, 0, "Download", Download::globalInstance());
|
||||
qmlRegisterSingletonInstance("network", 1, 0, "Network", Network::globalInstance());
|
||||
qmlRegisterSingletonInstance("localdocs", 1, 0, "LocalDocs", LocalDocs::globalInstance());
|
||||
qmlRegisterSingletonInstance("toollist", 1, 0, "ToolList", ToolModel::globalInstance());
|
||||
qmlRegisterUncreatableMetaObject(ToolEnums::staticMetaObject, "toolenums", 1, 0, "ToolEnums", "Error: only enums");
|
||||
qmlRegisterUncreatableMetaObject(MySettingsEnums::staticMetaObject, "mysettingsenums", 1, 0, "MySettingsEnums", "Error: only enums");
|
||||
|
||||
const QUrl url(u"qrc:/gpt4all/main.qml"_qs);
|
||||
|
||||
@@ -12,6 +12,8 @@ import network
|
||||
import gpt4all
|
||||
import localdocs
|
||||
import mysettings
|
||||
import toollist
|
||||
import toolenums
|
||||
|
||||
Window {
|
||||
id: window
|
||||
@@ -413,7 +415,11 @@ Window {
|
||||
|
||||
ColorOverlay {
|
||||
id: antennaColored
|
||||
visible: ModelList.selectableModels.count !== 0 && (currentChat.isServer || currentChat.modelInfo.isOnline || MySettings.networkIsActive)
|
||||
visible: ModelList.selectableModels.count !== 0
|
||||
&& (MySettings.networkIsActive
|
||||
|| currentChat.modelInfo.isOnline
|
||||
|| currentChat.isServer
|
||||
|| ToolList.privacyScope === ToolEnums.PrivacyScope.None)
|
||||
anchors.fill: antennaImage
|
||||
source: antennaImage
|
||||
color: theme.styledTextColor
|
||||
@@ -422,8 +428,10 @@ Window {
|
||||
return qsTr("The datalake is enabled")
|
||||
else if (currentChat.modelInfo.isOnline)
|
||||
return qsTr("Using a network model")
|
||||
else if (currentChat.modelInfo.isOnline)
|
||||
else if (currentChat.isServer)
|
||||
return qsTr("Server mode is enabled")
|
||||
else if (ToolList.privacyScope === ToolEnums.PrivacyScope.None)
|
||||
return qsTr("One or more enabled tools is not private")
|
||||
return ""
|
||||
}
|
||||
ToolTip.visible: maAntenna.containsMouse
|
||||
|
||||
@@ -323,15 +323,26 @@ void ModelInfo::setPromptTemplate(const QString &t)
|
||||
m_promptTemplate = t;
|
||||
}
|
||||
|
||||
QString ModelInfo::systemPrompt() const
|
||||
QString ModelInfo::toolTemplate() const
|
||||
{
|
||||
return MySettings::globalInstance()->modelSystemPrompt(*this);
|
||||
return MySettings::globalInstance()->modelToolTemplate(*this);
|
||||
}
|
||||
|
||||
void ModelInfo::setSystemPrompt(const QString &p)
|
||||
void ModelInfo::setToolTemplate(const QString &t)
|
||||
{
|
||||
if (shouldSaveMetadata()) MySettings::globalInstance()->setModelSystemPrompt(*this, p, true /*force*/);
|
||||
m_systemPrompt = p;
|
||||
if (shouldSaveMetadata()) MySettings::globalInstance()->setModelToolTemplate(*this, t, true /*force*/);
|
||||
m_toolTemplate = t;
|
||||
}
|
||||
|
||||
QString ModelInfo::systemPromptTemplate() const
|
||||
{
|
||||
return MySettings::globalInstance()->modelSystemPromptTemplate(*this);
|
||||
}
|
||||
|
||||
void ModelInfo::setSystemPromptTemplate(const QString &p)
|
||||
{
|
||||
if (shouldSaveMetadata()) MySettings::globalInstance()->setModelSystemPromptTemplate(*this, p, true /*force*/);
|
||||
m_systemPromptTemplate = p;
|
||||
}
|
||||
|
||||
QString ModelInfo::chatNamePrompt() const
|
||||
@@ -356,6 +367,17 @@ void ModelInfo::setSuggestedFollowUpPrompt(const QString &p)
|
||||
m_suggestedFollowUpPrompt = p;
|
||||
}
|
||||
|
||||
bool ModelInfo::isToolCalling() const
|
||||
{
|
||||
return MySettings::globalInstance()->modelIsToolCalling(*this);
|
||||
}
|
||||
|
||||
void ModelInfo::setIsToolCalling(bool b)
|
||||
{
|
||||
if (shouldSaveMetadata()) MySettings::globalInstance()->setModelIsToolCalling(*this, b, true /*force*/);
|
||||
m_isToolCalling = b;
|
||||
}
|
||||
|
||||
bool ModelInfo::shouldSaveMetadata() const
|
||||
{
|
||||
return installed && (isClone() || isDiscovered() || description() == "" /*indicates sideloaded*/);
|
||||
@@ -385,9 +407,11 @@ QVariantMap ModelInfo::getFields() const
|
||||
{ "repeatPenalty", m_repeatPenalty },
|
||||
{ "repeatPenaltyTokens", m_repeatPenaltyTokens },
|
||||
{ "promptTemplate", m_promptTemplate },
|
||||
{ "systemPrompt", m_systemPrompt },
|
||||
{ "toolTemplate", m_toolTemplate },
|
||||
{ "systemPromptTemplate",m_systemPromptTemplate },
|
||||
{ "chatNamePrompt", m_chatNamePrompt },
|
||||
{ "suggestedFollowUpPrompt", m_suggestedFollowUpPrompt },
|
||||
{ "isToolCalling", m_isToolCalling },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -504,7 +528,9 @@ ModelList::ModelList()
|
||||
connect(MySettings::globalInstance(), &MySettings::repeatPenaltyChanged, this, &ModelList::updateDataForSettings);
|
||||
connect(MySettings::globalInstance(), &MySettings::repeatPenaltyTokensChanged, this, &ModelList::updateDataForSettings);;
|
||||
connect(MySettings::globalInstance(), &MySettings::promptTemplateChanged, this, &ModelList::updateDataForSettings);
|
||||
connect(MySettings::globalInstance(), &MySettings::toolTemplateChanged, this, &ModelList::updateDataForSettings);
|
||||
connect(MySettings::globalInstance(), &MySettings::systemPromptChanged, this, &ModelList::updateDataForSettings);
|
||||
connect(MySettings::globalInstance(), &MySettings::isToolCallingChanged, this, &ModelList::updateDataForSettings);
|
||||
connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this, &ModelList::handleSslErrors);
|
||||
|
||||
updateModelsFromJson();
|
||||
@@ -776,8 +802,10 @@ QVariant ModelList::dataInternal(const ModelInfo *info, int role) const
|
||||
return info->repeatPenaltyTokens();
|
||||
case PromptTemplateRole:
|
||||
return info->promptTemplate();
|
||||
case ToolTemplateRole:
|
||||
return info->toolTemplate();
|
||||
case SystemPromptRole:
|
||||
return info->systemPrompt();
|
||||
return info->systemPromptTemplate();
|
||||
case ChatNamePromptRole:
|
||||
return info->chatNamePrompt();
|
||||
case SuggestedFollowUpPromptRole:
|
||||
@@ -788,7 +816,8 @@ QVariant ModelList::dataInternal(const ModelInfo *info, int role) const
|
||||
return info->downloads();
|
||||
case RecencyRole:
|
||||
return info->recency();
|
||||
|
||||
case IsToolCallingRole:
|
||||
return info->isToolCalling();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
@@ -952,8 +981,10 @@ void ModelList::updateData(const QString &id, const QVector<QPair<int, QVariant>
|
||||
info->setRepeatPenaltyTokens(value.toInt()); break;
|
||||
case PromptTemplateRole:
|
||||
info->setPromptTemplate(value.toString()); break;
|
||||
case ToolTemplateRole:
|
||||
info->setToolTemplate(value.toString()); break;
|
||||
case SystemPromptRole:
|
||||
info->setSystemPrompt(value.toString()); break;
|
||||
info->setSystemPromptTemplate(value.toString()); break;
|
||||
case ChatNamePromptRole:
|
||||
info->setChatNamePrompt(value.toString()); break;
|
||||
case SuggestedFollowUpPromptRole:
|
||||
@@ -982,6 +1013,8 @@ void ModelList::updateData(const QString &id, const QVector<QPair<int, QVariant>
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IsToolCallingRole:
|
||||
info->setIsToolCalling(value.toBool()); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1107,7 +1140,8 @@ QString ModelList::clone(const ModelInfo &model)
|
||||
{ ModelList::RepeatPenaltyRole, model.repeatPenalty() },
|
||||
{ ModelList::RepeatPenaltyTokensRole, model.repeatPenaltyTokens() },
|
||||
{ ModelList::PromptTemplateRole, model.promptTemplate() },
|
||||
{ ModelList::SystemPromptRole, model.systemPrompt() },
|
||||
{ ModelList::ToolTemplateRole, model.toolTemplate() },
|
||||
{ ModelList::SystemPromptRole, model.systemPromptTemplate() },
|
||||
{ ModelList::ChatNamePromptRole, model.chatNamePrompt() },
|
||||
{ ModelList::SuggestedFollowUpPromptRole, model.suggestedFollowUpPrompt() },
|
||||
};
|
||||
@@ -1551,8 +1585,12 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
|
||||
data.append({ ModelList::RepeatPenaltyTokensRole, obj["repeatPenaltyTokens"].toInt() });
|
||||
if (obj.contains("promptTemplate"))
|
||||
data.append({ ModelList::PromptTemplateRole, obj["promptTemplate"].toString() });
|
||||
if (obj.contains("toolTemplate"))
|
||||
data.append({ ModelList::ToolTemplateRole, obj["toolTemplate"].toString() });
|
||||
if (obj.contains("systemPrompt"))
|
||||
data.append({ ModelList::SystemPromptRole, obj["systemPrompt"].toString() });
|
||||
if (obj.contains("isToolCalling"))
|
||||
data.append({ ModelList::IsToolCallingRole, obj["isToolCalling"].toBool() });
|
||||
updateData(id, data);
|
||||
}
|
||||
|
||||
@@ -1852,6 +1890,10 @@ void ModelList::updateModelsFromSettings()
|
||||
const QString promptTemplate = settings.value(g + "/promptTemplate").toString();
|
||||
data.append({ ModelList::PromptTemplateRole, promptTemplate });
|
||||
}
|
||||
if (settings.contains(g + "/toolTemplate")) {
|
||||
const QString toolTemplate = settings.value(g + "/toolTemplate").toString();
|
||||
data.append({ ModelList::ToolTemplateRole, toolTemplate });
|
||||
}
|
||||
if (settings.contains(g + "/systemPrompt")) {
|
||||
const QString systemPrompt = settings.value(g + "/systemPrompt").toString();
|
||||
data.append({ ModelList::SystemPromptRole, systemPrompt });
|
||||
@@ -1864,6 +1906,10 @@ void ModelList::updateModelsFromSettings()
|
||||
const QString suggestedFollowUpPrompt = settings.value(g + "/suggestedFollowUpPrompt").toString();
|
||||
data.append({ ModelList::SuggestedFollowUpPromptRole, suggestedFollowUpPrompt });
|
||||
}
|
||||
if (settings.contains(g + "/isToolCalling")) {
|
||||
const bool isToolCalling = settings.value(g + "/isToolCalling").toBool();
|
||||
data.append({ ModelList::IsToolCallingRole, isToolCalling });
|
||||
}
|
||||
updateData(id, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,12 +68,14 @@ struct ModelInfo {
|
||||
Q_PROPERTY(double repeatPenalty READ repeatPenalty WRITE setRepeatPenalty)
|
||||
Q_PROPERTY(int repeatPenaltyTokens READ repeatPenaltyTokens WRITE setRepeatPenaltyTokens)
|
||||
Q_PROPERTY(QString promptTemplate READ promptTemplate WRITE setPromptTemplate)
|
||||
Q_PROPERTY(QString systemPrompt READ systemPrompt WRITE setSystemPrompt)
|
||||
Q_PROPERTY(QString toolTemplate READ toolTemplate WRITE setToolTemplate)
|
||||
Q_PROPERTY(QString systemPromptTemplate READ systemPromptTemplate WRITE setSystemPromptTemplate)
|
||||
Q_PROPERTY(QString chatNamePrompt READ chatNamePrompt WRITE setChatNamePrompt)
|
||||
Q_PROPERTY(QString suggestedFollowUpPrompt READ suggestedFollowUpPrompt WRITE setSuggestedFollowUpPrompt)
|
||||
Q_PROPERTY(int likes READ likes WRITE setLikes)
|
||||
Q_PROPERTY(int downloads READ downloads WRITE setDownloads)
|
||||
Q_PROPERTY(QDateTime recency READ recency WRITE setRecency)
|
||||
Q_PROPERTY(bool isToolCalling READ isToolCalling WRITE setIsToolCalling)
|
||||
|
||||
public:
|
||||
enum HashAlgorithm {
|
||||
@@ -117,6 +119,9 @@ public:
|
||||
QDateTime recency() const;
|
||||
void setRecency(const QDateTime &r);
|
||||
|
||||
bool isToolCalling() const;
|
||||
void setIsToolCalling(bool b);
|
||||
|
||||
QString dirpath;
|
||||
QString filesize;
|
||||
QByteArray hash;
|
||||
@@ -178,8 +183,11 @@ public:
|
||||
void setRepeatPenaltyTokens(int t);
|
||||
QString promptTemplate() const;
|
||||
void setPromptTemplate(const QString &t);
|
||||
QString systemPrompt() const;
|
||||
void setSystemPrompt(const QString &p);
|
||||
QString toolTemplate() const;
|
||||
void setToolTemplate(const QString &t);
|
||||
QString systemPromptTemplate() const;
|
||||
void setSystemPromptTemplate(const QString &p);
|
||||
// FIXME (adam): The chatname and suggested follow-up should also be templates I guess?
|
||||
QString chatNamePrompt() const;
|
||||
void setChatNamePrompt(const QString &p);
|
||||
QString suggestedFollowUpPrompt() const;
|
||||
@@ -215,9 +223,11 @@ private:
|
||||
double m_repeatPenalty = 1.18;
|
||||
int m_repeatPenaltyTokens = 64;
|
||||
QString m_promptTemplate = "### Human:\n%1\n\n### Assistant:\n";
|
||||
QString m_systemPrompt = "### System:\nYou are an AI assistant who gives a quality response to whatever humans ask of you.\n\n";
|
||||
QString m_toolTemplate = "";
|
||||
QString m_systemPromptTemplate = "### System:\nYou are an AI assistant who gives a quality response to whatever humans ask of you.\n\n";
|
||||
QString m_chatNamePrompt = "Describe the above conversation in seven words or less.";
|
||||
QString m_suggestedFollowUpPrompt = "Suggest three very short factual follow-up questions that have not been answered yet or cannot be found inspired by the previous conversation and excerpts.";
|
||||
bool m_isToolCalling = false;
|
||||
friend class MySettings;
|
||||
};
|
||||
Q_DECLARE_METATYPE(ModelInfo)
|
||||
@@ -339,13 +349,15 @@ public:
|
||||
RepeatPenaltyRole,
|
||||
RepeatPenaltyTokensRole,
|
||||
PromptTemplateRole,
|
||||
ToolTemplateRole,
|
||||
SystemPromptRole,
|
||||
ChatNamePromptRole,
|
||||
SuggestedFollowUpPromptRole,
|
||||
MinPRole,
|
||||
LikesRole,
|
||||
DownloadsRole,
|
||||
RecencyRole
|
||||
RecencyRole,
|
||||
IsToolCallingRole
|
||||
};
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override
|
||||
@@ -393,6 +405,7 @@ public:
|
||||
roles[RepeatPenaltyRole] = "repeatPenalty";
|
||||
roles[RepeatPenaltyTokensRole] = "repeatPenaltyTokens";
|
||||
roles[PromptTemplateRole] = "promptTemplate";
|
||||
roles[ToolTemplateRole] = "toolTemplate";
|
||||
roles[SystemPromptRole] = "systemPrompt";
|
||||
roles[ChatNamePromptRole] = "chatNamePrompt";
|
||||
roles[SuggestedFollowUpPromptRole] = "suggestedFollowUpPrompt";
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "mysettings.h"
|
||||
|
||||
#include "../gpt4all-backend/llmodel.h"
|
||||
#include "tool.h"
|
||||
#include "toolmodel.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
@@ -18,11 +20,13 @@
|
||||
#include <QtLogging>
|
||||
|
||||
#include <algorithm>
|
||||
#include <jinja2cpp/template.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
using namespace ToolEnums;
|
||||
|
||||
// used only for settings serialization, do not translate
|
||||
static const QStringList suggestionModeNames { "LocalDocsOnly", "On", "Off" };
|
||||
@@ -44,22 +48,26 @@ static const QString languageAndLocale = "System Locale";
|
||||
} // namespace defaults
|
||||
|
||||
static const QVariantMap basicDefaults {
|
||||
{ "chatTheme", QVariant::fromValue(ChatTheme::Light) },
|
||||
{ "fontSize", QVariant::fromValue(FontSize::Small) },
|
||||
{ "lastVersionStarted", "" },
|
||||
{ "networkPort", 4891, },
|
||||
{ "saveChatsContext", false },
|
||||
{ "serverChat", false },
|
||||
{ "userDefaultModel", "Application default" },
|
||||
{ "suggestionMode", QVariant::fromValue(SuggestionMode::LocalDocsOnly) },
|
||||
{ "localdocs/chunkSize", 512 },
|
||||
{ "localdocs/retrievalSize", 3 },
|
||||
{ "localdocs/showReferences", true },
|
||||
{ "localdocs/fileExtensions", QStringList { "txt", "pdf", "md", "rst" } },
|
||||
{ "localdocs/useRemoteEmbed", false },
|
||||
{ "localdocs/nomicAPIKey", "" },
|
||||
{ "localdocs/embedDevice", "Auto" },
|
||||
{ "network/attribution", "" },
|
||||
{ "chatTheme", QVariant::fromValue(ChatTheme::Light) },
|
||||
{ "fontSize", QVariant::fromValue(FontSize::Small) },
|
||||
{ "lastVersionStarted", "" },
|
||||
{ "networkPort", 4891, },
|
||||
{ "saveChatsContext", false },
|
||||
{ "serverChat", false },
|
||||
{ "userDefaultModel", "Application default" },
|
||||
{ "suggestionMode", QVariant::fromValue(SuggestionMode::SourceExcerptsOnly) },
|
||||
{ "localdocs/chunkSize", 512 },
|
||||
{ "localdocs/retrievalSize", 3 },
|
||||
{ "localdocs/showReferences", true },
|
||||
{ "localdocs/fileExtensions", QStringList { "txt", "pdf", "md", "rst" } },
|
||||
{ "localdocs/useRemoteEmbed", false },
|
||||
{ "localdocs/nomicAPIKey", "" },
|
||||
{ "localdocs/embedDevice", "Auto" },
|
||||
{ "network/attribution", "" },
|
||||
{ "websearch/retrievalSize", 2 },
|
||||
{ "websearch/usageMode", QVariant::fromValue(UsageMode::Disabled) },
|
||||
{ "websearch/confirmationMode", QVariant::fromValue(ConfirmationMode::NoConfirmation) },
|
||||
{ "bravesearch/APIKey", "" },
|
||||
};
|
||||
|
||||
static QString defaultLocalModelsPath()
|
||||
@@ -194,7 +202,9 @@ void MySettings::restoreModelDefaults(const ModelInfo &info)
|
||||
setModelRepeatPenalty(info, info.m_repeatPenalty);
|
||||
setModelRepeatPenaltyTokens(info, info.m_repeatPenaltyTokens);
|
||||
setModelPromptTemplate(info, info.m_promptTemplate);
|
||||
setModelSystemPrompt(info, info.m_systemPrompt);
|
||||
setModelToolTemplate(info, info.m_toolTemplate);
|
||||
setModelIsToolCalling(info, info.m_isToolCalling);
|
||||
setModelSystemPromptTemplate(info, info.m_systemPromptTemplate);
|
||||
setModelChatNamePrompt(info, info.m_chatNamePrompt);
|
||||
setModelSuggestedFollowUpPrompt(info, info.m_suggestedFollowUpPrompt);
|
||||
}
|
||||
@@ -226,6 +236,14 @@ void MySettings::restoreLocalDocsDefaults()
|
||||
setLocalDocsEmbedDevice(basicDefaults.value("localdocs/embedDevice").toString());
|
||||
}
|
||||
|
||||
void MySettings::restoreWebSearchDefaults()
|
||||
{
|
||||
setWebSearchUsageMode(basicDefaults.value("websearch/usageMode").value<UsageMode>());
|
||||
setWebSearchRetrievalSize(basicDefaults.value("websearch/retrievalSize").toInt());
|
||||
setWebSearchConfirmationMode(basicDefaults.value("websearch/confirmationMode").value<ConfirmationMode>());
|
||||
setBraveSearchAPIKey(basicDefaults.value("bravesearch/APIKey").toString());
|
||||
}
|
||||
|
||||
void MySettings::eraseModel(const ModelInfo &info)
|
||||
{
|
||||
m_settings.remove(u"model-%1"_s.arg(info.id()));
|
||||
@@ -296,7 +314,9 @@ int MySettings::modelGpuLayers (const ModelInfo &info) const
|
||||
double MySettings::modelRepeatPenalty (const ModelInfo &info) const { return getModelSetting("repeatPenalty", info).toDouble(); }
|
||||
int MySettings::modelRepeatPenaltyTokens (const ModelInfo &info) const { return getModelSetting("repeatPenaltyTokens", info).toInt(); }
|
||||
QString MySettings::modelPromptTemplate (const ModelInfo &info) const { return getModelSetting("promptTemplate", info).toString(); }
|
||||
QString MySettings::modelSystemPrompt (const ModelInfo &info) const { return getModelSetting("systemPrompt", info).toString(); }
|
||||
QString MySettings::modelToolTemplate (const ModelInfo &info) const { return getModelSetting("toolTemplate", info).toString(); }
|
||||
bool MySettings::modelIsToolCalling (const ModelInfo &info) const { return getModelSetting("isToolCalling", info).toBool(); }
|
||||
QString MySettings::modelSystemPromptTemplate (const ModelInfo &info) const { return getModelSetting("systemPrompt", info).toString(); }
|
||||
QString MySettings::modelChatNamePrompt (const ModelInfo &info) const { return getModelSetting("chatNamePrompt", info).toString(); }
|
||||
QString MySettings::modelSuggestedFollowUpPrompt(const ModelInfo &info) const { return getModelSetting("suggestedFollowUpPrompt", info).toString(); }
|
||||
|
||||
@@ -405,7 +425,17 @@ void MySettings::setModelPromptTemplate(const ModelInfo &info, const QString &va
|
||||
setModelSetting("promptTemplate", info, value, force, true);
|
||||
}
|
||||
|
||||
void MySettings::setModelSystemPrompt(const ModelInfo &info, const QString &value, bool force)
|
||||
void MySettings::setModelToolTemplate(const ModelInfo &info, const QString &value, bool force)
|
||||
{
|
||||
setModelSetting("toolTemplate", info, value, force, true);
|
||||
}
|
||||
|
||||
void MySettings::setModelIsToolCalling(const ModelInfo &info, bool value, bool force)
|
||||
{
|
||||
setModelSetting("isToolCalling", info, value, force, true);
|
||||
}
|
||||
|
||||
void MySettings::setModelSystemPromptTemplate(const ModelInfo &info, const QString &value, bool force)
|
||||
{
|
||||
setModelSetting("systemPrompt", info, value, force, true);
|
||||
}
|
||||
@@ -456,6 +486,10 @@ bool MySettings::localDocsUseRemoteEmbed() const { return getBasicSetting
|
||||
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(); }
|
||||
QString MySettings::braveSearchAPIKey() const { return getBasicSetting("bravesearch/APIKey" ).toString(); }
|
||||
int MySettings::webSearchRetrievalSize() const { return getBasicSetting("websearch/retrievalSize").toInt(); }
|
||||
UsageMode MySettings::webSearchUsageMode() const { return getBasicSetting("websearch/usageMode").value<UsageMode>(); }
|
||||
ConfirmationMode MySettings::webSearchConfirmationMode() const { return getBasicSetting("websearch/confirmationMode").value<ConfirmationMode>(); }
|
||||
|
||||
ChatTheme MySettings::chatTheme() const { return ChatTheme (getEnumSetting("chatTheme", chatThemeNames)); }
|
||||
FontSize MySettings::fontSize() const { return FontSize (getEnumSetting("fontSize", fontSizeNames)); }
|
||||
@@ -474,6 +508,10 @@ void MySettings::setLocalDocsUseRemoteEmbed(bool value) { setBasic
|
||||
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"); }
|
||||
void MySettings::setBraveSearchAPIKey(const QString &value) { setBasicSetting("bravesearch/APIKey", value, "braveSearchAPIKey"); }
|
||||
void MySettings::setWebSearchRetrievalSize(int value) { setBasicSetting("websearch/retrievalSize", value, "webSearchRetrievalSize"); }
|
||||
void MySettings::setWebSearchUsageMode(ToolEnums::UsageMode value) { setBasicSetting("websearch/usageMode", int(value), "webSearchUsageMode"); }
|
||||
void MySettings::setWebSearchConfirmationMode(ToolEnums::ConfirmationMode value) { setBasicSetting("websearch/confirmationMode", int(value), "webSearchConfirmationMode"); }
|
||||
|
||||
void MySettings::setChatTheme(ChatTheme value) { setBasicSetting("chatTheme", chatThemeNames .value(int(value))); }
|
||||
void MySettings::setFontSize(FontSize value) { setBasicSetting("fontSize", fontSizeNames .value(int(value))); }
|
||||
@@ -667,3 +705,49 @@ void MySettings::setLanguageAndLocale(const QString &bcp47Name)
|
||||
QLocale::setDefault(locale);
|
||||
emit languageAndLocaleChanged();
|
||||
}
|
||||
|
||||
QString MySettings::validateModelSystemPromptTemplate(const QString &proposedTemplate)
|
||||
{
|
||||
QString error;
|
||||
systemPromptInternal(proposedTemplate, error);
|
||||
return error;
|
||||
}
|
||||
|
||||
QString MySettings::modelSystemPrompt(const ModelInfo &info, QString &error)
|
||||
{
|
||||
return systemPromptInternal(modelSystemPromptTemplate(info), error);
|
||||
}
|
||||
|
||||
QString MySettings::systemPromptInternal(const QString &proposedTemplate, QString &error)
|
||||
{
|
||||
jinja2::ValuesMap params;
|
||||
params.insert({"currentDate", QDate::currentDate().toString().toStdString()});
|
||||
|
||||
jinja2::ValuesList toolList;
|
||||
const int toolCount = ToolModel::globalInstance()->count();
|
||||
for (int i = 0; i < toolCount; ++i) {
|
||||
Tool *t = ToolModel::globalInstance()->get(i);
|
||||
// FIXME: For now we don't tell the model about the localdocs search in the system prompt because
|
||||
// it will try to call the localdocs search even if no collection is selected. Ideally, we need
|
||||
// away to update model to whether a tool is enabled/disabled either via reprocessing the system
|
||||
// prompt or sending a system message as it happens
|
||||
if (t->usageMode() != UsageMode::Disabled && t->function() != "localdocs_search")
|
||||
toolList.push_back(t->jinjaValue());
|
||||
}
|
||||
params.insert({"toolList", toolList});
|
||||
|
||||
QString systemPrompt;
|
||||
jinja2::Template t;
|
||||
const auto loadResult = t.Load(proposedTemplate.toStdString(), "systemPromptTemplate" /*Used in error messages*/);
|
||||
if (!loadResult) {
|
||||
error = QString::fromStdString(loadResult.error().ToString());
|
||||
return systemPrompt;
|
||||
}
|
||||
|
||||
const auto renderResult = t.RenderAsString(params);
|
||||
if (renderResult)
|
||||
systemPrompt = QString::fromStdString(renderResult.value());
|
||||
else
|
||||
error = QString::fromStdString(renderResult.error().ToString());
|
||||
return systemPrompt;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define MYSETTINGS_H
|
||||
|
||||
#include "modellist.h" // IWYU pragma: keep
|
||||
#include "tool.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
@@ -21,9 +22,9 @@ namespace MySettingsEnums {
|
||||
* ApplicationSettings.qml, as well as the corresponding name lists in mysettings.cpp */
|
||||
|
||||
enum class SuggestionMode {
|
||||
LocalDocsOnly = 0,
|
||||
On = 1,
|
||||
Off = 2,
|
||||
SourceExcerptsOnly = 0,
|
||||
On = 1,
|
||||
Off = 2,
|
||||
};
|
||||
Q_ENUM_NS(SuggestionMode)
|
||||
|
||||
@@ -72,6 +73,10 @@ class MySettings : public QObject
|
||||
Q_PROPERTY(int networkPort READ networkPort WRITE setNetworkPort NOTIFY networkPortChanged)
|
||||
Q_PROPERTY(SuggestionMode suggestionMode READ suggestionMode WRITE setSuggestionMode NOTIFY suggestionModeChanged)
|
||||
Q_PROPERTY(QStringList uiLanguages MEMBER m_uiLanguages CONSTANT)
|
||||
Q_PROPERTY(int webSearchRetrievalSize READ webSearchRetrievalSize WRITE setWebSearchRetrievalSize NOTIFY webSearchRetrievalSizeChanged)
|
||||
Q_PROPERTY(ToolEnums::UsageMode webSearchUsageMode READ webSearchUsageMode WRITE setWebSearchUsageMode NOTIFY webSearchUsageModeChanged)
|
||||
Q_PROPERTY(ToolEnums::ConfirmationMode webSearchConfirmationMode READ webSearchConfirmationMode WRITE setWebSearchConfirmationMode NOTIFY webSearchConfirmationModeChanged)
|
||||
Q_PROPERTY(QString braveSearchAPIKey READ braveSearchAPIKey WRITE setBraveSearchAPIKey NOTIFY braveSearchAPIKeyChanged)
|
||||
|
||||
public:
|
||||
static MySettings *globalInstance();
|
||||
@@ -80,6 +85,7 @@ public:
|
||||
Q_INVOKABLE void restoreModelDefaults(const ModelInfo &info);
|
||||
Q_INVOKABLE void restoreApplicationDefaults();
|
||||
Q_INVOKABLE void restoreLocalDocsDefaults();
|
||||
Q_INVOKABLE void restoreWebSearchDefaults();
|
||||
|
||||
// Model/Character settings
|
||||
void eraseModel(const ModelInfo &info);
|
||||
@@ -125,8 +131,12 @@ public:
|
||||
Q_INVOKABLE void setModelRepeatPenaltyTokens(const ModelInfo &info, int value, bool force = false);
|
||||
QString modelPromptTemplate(const ModelInfo &info) const;
|
||||
Q_INVOKABLE void setModelPromptTemplate(const ModelInfo &info, const QString &value, bool force = false);
|
||||
QString modelSystemPrompt(const ModelInfo &info) const;
|
||||
Q_INVOKABLE void setModelSystemPrompt(const ModelInfo &info, const QString &value, bool force = false);
|
||||
QString modelToolTemplate(const ModelInfo &info) const;
|
||||
Q_INVOKABLE void setModelToolTemplate(const ModelInfo &info, const QString &value, bool force = false);
|
||||
bool modelIsToolCalling(const ModelInfo &info) const;
|
||||
Q_INVOKABLE void setModelIsToolCalling(const ModelInfo &info, bool value, bool force = false);
|
||||
QString modelSystemPromptTemplate(const ModelInfo &info) const;
|
||||
Q_INVOKABLE void setModelSystemPromptTemplate(const ModelInfo &info, const QString &value, bool force = false);
|
||||
int modelContextLength(const ModelInfo &info) const;
|
||||
Q_INVOKABLE void setModelContextLength(const ModelInfo &info, int value, bool force = false);
|
||||
int modelGpuLayers(const ModelInfo &info) const;
|
||||
@@ -185,6 +195,16 @@ public:
|
||||
QString localDocsEmbedDevice() const;
|
||||
void setLocalDocsEmbedDevice(const QString &value);
|
||||
|
||||
// Web search settings
|
||||
int webSearchRetrievalSize() const;
|
||||
void setWebSearchRetrievalSize(int value);
|
||||
ToolEnums::UsageMode webSearchUsageMode() const;
|
||||
void setWebSearchUsageMode(ToolEnums::UsageMode value);
|
||||
ToolEnums::ConfirmationMode webSearchConfirmationMode() const;
|
||||
void setWebSearchConfirmationMode(ToolEnums::ConfirmationMode value);
|
||||
QString braveSearchAPIKey() const;
|
||||
void setBraveSearchAPIKey(const QString &value);
|
||||
|
||||
// Network settings
|
||||
QString networkAttribution() const;
|
||||
void setNetworkAttribution(const QString &value);
|
||||
@@ -197,6 +217,10 @@ public:
|
||||
int networkPort() const;
|
||||
void setNetworkPort(int value);
|
||||
|
||||
// Jinja aware methods for validating and parsing/rendering the system prompt
|
||||
Q_INVOKABLE QString validateModelSystemPromptTemplate(const QString &proposedTemplate);
|
||||
QString modelSystemPrompt(const ModelInfo &info, QString &error);
|
||||
|
||||
Q_SIGNALS:
|
||||
void nameChanged(const ModelInfo &info);
|
||||
void filenameChanged(const ModelInfo &info);
|
||||
@@ -212,9 +236,11 @@ Q_SIGNALS:
|
||||
void repeatPenaltyChanged(const ModelInfo &info);
|
||||
void repeatPenaltyTokensChanged(const ModelInfo &info);
|
||||
void promptTemplateChanged(const ModelInfo &info);
|
||||
void toolTemplateChanged(const ModelInfo &info);
|
||||
void systemPromptChanged(const ModelInfo &info);
|
||||
void chatNamePromptChanged(const ModelInfo &info);
|
||||
void suggestedFollowUpPromptChanged(const ModelInfo &info);
|
||||
void isToolCallingChanged(const ModelInfo &info);
|
||||
void threadCountChanged();
|
||||
void saveChatsContextChanged();
|
||||
void serverChatChanged();
|
||||
@@ -239,6 +265,11 @@ Q_SIGNALS:
|
||||
void deviceChanged();
|
||||
void suggestionModeChanged();
|
||||
void languageAndLocaleChanged();
|
||||
void webSearchRetrievalSizeChanged();
|
||||
// FIXME: These are never emitted along with a lot of the signals above probably with all kinds of bugs!!
|
||||
void webSearchUsageModeChanged();
|
||||
void webSearchConfirmationModeChanged();
|
||||
void braveSearchAPIKeyChanged();
|
||||
|
||||
private:
|
||||
QSettings m_settings;
|
||||
@@ -260,6 +291,7 @@ private:
|
||||
void setModelSetting(const QString &name, const ModelInfo &info, const QVariant &value, bool force,
|
||||
bool signal = false);
|
||||
QString filePathForLocale(const QLocale &locale);
|
||||
QString systemPromptInternal(const QString &proposedTemplate, QString &error);
|
||||
};
|
||||
|
||||
#endif // MYSETTINGS_H
|
||||
|
||||
@@ -350,17 +350,26 @@ MySettingsTab {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
// NOTE: indices match values of SuggestionMode enum, keep them in sync
|
||||
model: ListModel {
|
||||
ListElement { name: qsTr("When chatting with LocalDocs") }
|
||||
ListElement { name: qsTr("When source excerpts are cited") }
|
||||
ListElement { name: qsTr("Whenever possible") }
|
||||
ListElement { name: qsTr("Never") }
|
||||
}
|
||||
function updateModel() {
|
||||
suggestionModeBox.currentIndex = MySettings.suggestionMode;
|
||||
}
|
||||
Accessible.name: suggestionModeLabel.text
|
||||
Accessible.description: suggestionModeLabel.helpText
|
||||
onActivated: {
|
||||
MySettings.suggestionMode = suggestionModeBox.currentIndex;
|
||||
}
|
||||
Component.onCompleted: {
|
||||
suggestionModeBox.currentIndex = MySettings.suggestionMode;
|
||||
suggestionModeBox.updateModel();
|
||||
}
|
||||
Connections {
|
||||
target: MySettings
|
||||
function onSuggestionModeChanged() {
|
||||
suggestionModeBox.updateModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
MySettingsLabel {
|
||||
|
||||
@@ -881,6 +881,8 @@ Rectangle {
|
||||
case Chat.PromptProcessing: return qsTr("processing ...")
|
||||
case Chat.ResponseGeneration: return qsTr("generating response ...");
|
||||
case Chat.GeneratingQuestions: return qsTr("generating questions ...");
|
||||
case Chat.ToolCalled: return qsTr("executing %1 ...").arg(currentChat.toolDescription);
|
||||
case Chat.ToolProcessing: return qsTr("processing %1 results ...").arg(currentChat.toolDescription);
|
||||
default: return ""; // handle unexpected values
|
||||
}
|
||||
}
|
||||
@@ -1104,7 +1106,7 @@ Rectangle {
|
||||
Layout.preferredWidth: childrenRect.width
|
||||
Layout.preferredHeight: childrenRect.height
|
||||
visible: {
|
||||
if (consolidatedSources.length === 0)
|
||||
if (sources.length === 0)
|
||||
return false
|
||||
if (!MySettings.localDocsShowReferences)
|
||||
return false
|
||||
@@ -1131,7 +1133,14 @@ Rectangle {
|
||||
sourceSize.width: 24
|
||||
sourceSize.height: 24
|
||||
mipmap: true
|
||||
source: "qrc:/gpt4all/icons/db.svg"
|
||||
source: {
|
||||
if (typeof sources === 'undefined'
|
||||
|| typeof sources[0] === 'undefined'
|
||||
|| sources[0].url === "")
|
||||
return "qrc:/gpt4all/icons/db.svg";
|
||||
else
|
||||
return "qrc:/gpt4all/icons/globe.svg";
|
||||
}
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
@@ -1142,7 +1151,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
Text {
|
||||
text: qsTr("%1 Sources").arg(consolidatedSources.length)
|
||||
text: qsTr("%1 Sources").arg(sources.length)
|
||||
padding: 0
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
font.bold: true
|
||||
@@ -1190,7 +1199,7 @@ Rectangle {
|
||||
Layout.column: 1
|
||||
Layout.topMargin: 5
|
||||
visible: {
|
||||
if (consolidatedSources.length === 0)
|
||||
if (sources.length === 0)
|
||||
return false
|
||||
if (!MySettings.localDocsShowReferences)
|
||||
return false
|
||||
@@ -1231,9 +1240,9 @@ Rectangle {
|
||||
id: flow
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
visible: consolidatedSources.length !== 0
|
||||
visible: sources.length !== 0
|
||||
Repeater {
|
||||
model: consolidatedSources
|
||||
model: sources
|
||||
|
||||
delegate: Rectangle {
|
||||
radius: 10
|
||||
@@ -1243,11 +1252,15 @@ Rectangle {
|
||||
|
||||
MouseArea {
|
||||
id: ma
|
||||
enabled: modelData.path !== ""
|
||||
enabled: modelData.path !== "" || modelData.url !== ""
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: function() {
|
||||
Qt.openUrlExternally(modelData.fileUri)
|
||||
if (modelData.url !== "") {
|
||||
console.log("opening url")
|
||||
Qt.openUrlExternally(modelData.url)
|
||||
} else
|
||||
Qt.openUrlExternally(modelData.fileUri)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1287,22 +1300,27 @@ Rectangle {
|
||||
Image {
|
||||
id: fileIcon
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
visible: modelData.favicon !== ""
|
||||
sourceSize.width: 24
|
||||
sourceSize.height: 24
|
||||
mipmap: true
|
||||
source: {
|
||||
if (modelData.file.toLowerCase().endsWith(".txt"))
|
||||
if (modelData.favicon !== "")
|
||||
return modelData.favicon;
|
||||
else if (modelData.file.toLowerCase().endsWith(".txt"))
|
||||
return "qrc:/gpt4all/icons/file-txt.svg"
|
||||
else if (modelData.file.toLowerCase().endsWith(".pdf"))
|
||||
return "qrc:/gpt4all/icons/file-pdf.svg"
|
||||
else if (modelData.file.toLowerCase().endsWith(".md"))
|
||||
return "qrc:/gpt4all/icons/file-md.svg"
|
||||
else
|
||||
else if (modelData.file !== "")
|
||||
return "qrc:/gpt4all/icons/file.svg"
|
||||
else
|
||||
return "qrc:/gpt4all/icons/globe.svg"
|
||||
}
|
||||
}
|
||||
ColorOverlay {
|
||||
visible: !fileIcon.visible
|
||||
anchors.fill: fileIcon
|
||||
source: fileIcon
|
||||
color: theme.textColor
|
||||
@@ -1310,7 +1328,7 @@ Rectangle {
|
||||
}
|
||||
Text {
|
||||
Layout.maximumWidth: 156
|
||||
text: modelData.collection !== "" ? modelData.collection : qsTr("LocalDocs")
|
||||
text: modelData.collection !== "" ? modelData.collection : modelData.title
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
font.bold: true
|
||||
color: theme.styledTextColor
|
||||
@@ -1326,7 +1344,7 @@ Rectangle {
|
||||
Layout.fillHeight: true
|
||||
Layout.maximumWidth: 180
|
||||
Layout.maximumHeight: 55 - title.height
|
||||
text: modelData.file
|
||||
text: modelData.file !== "" ? modelData.file : modelData.url
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeSmall
|
||||
elide: Qt.ElideRight
|
||||
@@ -1343,7 +1361,7 @@ Rectangle {
|
||||
return false;
|
||||
if (MySettings.suggestionMode === 2) // Off
|
||||
return false;
|
||||
if (MySettings.suggestionMode === 0 && consolidatedSources.length === 0) // LocalDocs only
|
||||
if (MySettings.suggestionMode === 0 && sources.length === 0) // LocalDocs only
|
||||
return false;
|
||||
return currentChat.responseState === Chat.GeneratingQuestions || currentChat.generatedQuestions.length !== 0;
|
||||
}
|
||||
|
||||
@@ -255,13 +255,14 @@ MySettingsTab {
|
||||
MySettingsLabel {
|
||||
id: chunkLabel
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Document snippet size (characters)")
|
||||
helpText: qsTr("Number of characters per document snippet. Larger numbers increase likelihood of factual responses, but also result in slower generation.")
|
||||
text: qsTr("Document excerpt size (characters)")
|
||||
helpText: qsTr("Number of characters per document excerpt. Larger numbers increase likelihood of factual responses, but also result in slower generation.")
|
||||
}
|
||||
|
||||
MyTextField {
|
||||
id: chunkSizeTextField
|
||||
text: MySettings.localDocsChunkSize
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
validator: IntValidator {
|
||||
bottom: 1
|
||||
}
|
||||
@@ -281,13 +282,14 @@ MySettingsTab {
|
||||
Layout.topMargin: 15
|
||||
MySettingsLabel {
|
||||
id: contextItemsPerPrompt
|
||||
text: qsTr("Max document snippets per prompt")
|
||||
helpText: qsTr("Max best N matches of retrieved document snippets to add to the context for prompt. Larger numbers increase likelihood of factual responses, but also result in slower generation.")
|
||||
text: qsTr("Max source excerpts per prompt")
|
||||
helpText: qsTr("Max best N matches of retrieved source excerpts to add to the context for prompt. Larger numbers increase likelihood of factual responses, but also result in slower generation.")
|
||||
|
||||
}
|
||||
|
||||
MyTextField {
|
||||
text: MySettings.localDocsRetrievalSize
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
validator: IntValidator {
|
||||
bottom: 1
|
||||
}
|
||||
|
||||
@@ -154,18 +154,56 @@ MySettingsTab {
|
||||
}
|
||||
|
||||
MySettingsLabel {
|
||||
visible: !root.currentModelInfo.isOnline
|
||||
text: qsTr("System Prompt")
|
||||
helpText: qsTr("Prefixed at the beginning of every conversation. Must contain the appropriate framing tokens.")
|
||||
Layout.row: 7
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 1
|
||||
Layout.topMargin: 15
|
||||
id: isToolCallingLabel
|
||||
text: qsTr("Is Tool Calling Model")
|
||||
helpText: qsTr("Whether the model is capable of tool calling and has tool calling instructions in system prompt.")
|
||||
}
|
||||
|
||||
MyCheckBox {
|
||||
Layout.row: 7
|
||||
Layout.column: 1
|
||||
Layout.topMargin: 15
|
||||
id: isToolCallingBox
|
||||
checked: root.currentModelInfo.isToolCalling
|
||||
onClicked: {
|
||||
MySettings.setModelIsToolCalling(root.currentModelInfo, isToolCallingBox.checked);
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.row: 8
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 2
|
||||
Layout.topMargin: 15
|
||||
spacing: 10
|
||||
MySettingsLabel {
|
||||
text: qsTr("System Prompt Template")
|
||||
helpText: qsTr("Prefixed at the beginning of every conversation. Must contain the appropriate framing tokens.")
|
||||
}
|
||||
MySettingsLabel {
|
||||
id: systemPromptTemplateError
|
||||
color: theme.textErrorColor
|
||||
wrapMode: TextArea.Wrap
|
||||
Timer {
|
||||
id: errorTimer
|
||||
interval: 500 // 500 ms delay
|
||||
repeat: false
|
||||
property string text: ""
|
||||
onTriggered: {
|
||||
systemPromptTemplateError.text = errorTimer.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: systemPrompt
|
||||
visible: !root.currentModelInfo.isOnline
|
||||
Layout.row: 8
|
||||
Layout.row: 9
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
@@ -174,28 +212,36 @@ MySettingsTab {
|
||||
MyTextArea {
|
||||
id: systemPromptArea
|
||||
anchors.fill: parent
|
||||
text: root.currentModelInfo.systemPrompt
|
||||
text: root.currentModelInfo.systemPromptTemplate
|
||||
Connections {
|
||||
target: MySettings
|
||||
function onSystemPromptChanged() {
|
||||
systemPromptArea.text = root.currentModelInfo.systemPrompt;
|
||||
systemPromptArea.text = root.currentModelInfo.systemPromptTemplate;
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: root
|
||||
function onCurrentModelInfoChanged() {
|
||||
systemPromptArea.text = root.currentModelInfo.systemPrompt;
|
||||
systemPromptArea.text = root.currentModelInfo.systemPromptTemplate;
|
||||
}
|
||||
}
|
||||
onTextChanged: {
|
||||
MySettings.setModelSystemPrompt(root.currentModelInfo, text)
|
||||
var errorString = MySettings.validateModelSystemPromptTemplate(text);
|
||||
if (errorString === "") {
|
||||
errorTimer.stop();
|
||||
systemPromptTemplateError.text = ""; // Clear any previous error
|
||||
MySettings.setModelSystemPromptTemplate(root.currentModelInfo, text);
|
||||
} else {
|
||||
errorTimer.text = errorString;
|
||||
errorTimer.restart();
|
||||
}
|
||||
}
|
||||
Accessible.role: Accessible.EditableText
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.row: 9
|
||||
Layout.row: 10
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 2
|
||||
Layout.topMargin: 15
|
||||
@@ -209,38 +255,38 @@ MySettingsTab {
|
||||
id: promptTemplateLabelHelp
|
||||
text: qsTr("Must contain the string \"%1\" to be replaced with the user's input.")
|
||||
color: theme.textErrorColor
|
||||
visible: templateTextArea.text.indexOf("%1") === -1
|
||||
visible: promptTemplateTextArea.text.indexOf("%1") === -1
|
||||
wrapMode: TextArea.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: promptTemplate
|
||||
Layout.row: 10
|
||||
Layout.row: 11
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: Math.max(100, templateTextArea.contentHeight + 20)
|
||||
Layout.minimumHeight: Math.max(100, promptTemplateTextArea.contentHeight + 20)
|
||||
color: "transparent"
|
||||
clip: true
|
||||
MyTextArea {
|
||||
id: templateTextArea
|
||||
id: promptTemplateTextArea
|
||||
anchors.fill: parent
|
||||
text: root.currentModelInfo.promptTemplate
|
||||
Connections {
|
||||
target: MySettings
|
||||
function onPromptTemplateChanged() {
|
||||
templateTextArea.text = root.currentModelInfo.promptTemplate;
|
||||
promptTemplateTextArea.text = root.currentModelInfo.promptTemplate;
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: root
|
||||
function onCurrentModelInfoChanged() {
|
||||
templateTextArea.text = root.currentModelInfo.promptTemplate;
|
||||
promptTemplateTextArea.text = root.currentModelInfo.promptTemplate;
|
||||
}
|
||||
}
|
||||
onTextChanged: {
|
||||
if (templateTextArea.text.indexOf("%1") !== -1) {
|
||||
if (promptTemplateTextArea.text.indexOf("%1") !== -1) {
|
||||
MySettings.setModelPromptTemplate(root.currentModelInfo, text)
|
||||
}
|
||||
}
|
||||
@@ -250,18 +296,65 @@ MySettingsTab {
|
||||
}
|
||||
}
|
||||
|
||||
MySettingsLabel {
|
||||
Layout.row: 12
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 2
|
||||
Layout.topMargin: 15
|
||||
id: toolTemplateLabel
|
||||
text: qsTr("Tool Template")
|
||||
helpText: qsTr("The template that allows tool calls to inject information into the context. Only enabled for tool calling models.")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: toolTemplate
|
||||
enabled: root.currentModelInfo.isToolCalling
|
||||
Layout.row: 13
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: Math.max(100, toolTemplateTextArea.contentHeight + 20)
|
||||
color: "transparent"
|
||||
clip: true
|
||||
MyTextArea {
|
||||
id: toolTemplateTextArea
|
||||
anchors.fill: parent
|
||||
text: root.currentModelInfo.toolTemplate
|
||||
Connections {
|
||||
target: MySettings
|
||||
function onToolTemplateChanged() {
|
||||
toolTemplateTextArea.text = root.currentModelInfo.toolTemplate;
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: root
|
||||
function onCurrentModelInfoChanged() {
|
||||
toolTemplateTextArea.text = root.currentModelInfo.toolTemplate;
|
||||
}
|
||||
}
|
||||
onTextChanged: {
|
||||
if (toolTemplateTextArea.text.indexOf("%1") !== -1) {
|
||||
MySettings.setModelToolTemplate(root.currentModelInfo, text)
|
||||
}
|
||||
}
|
||||
Accessible.role: Accessible.EditableText
|
||||
Accessible.name: toolTemplateLabel.text
|
||||
Accessible.description: toolTemplateLabel.text
|
||||
}
|
||||
}
|
||||
|
||||
MySettingsLabel {
|
||||
id: chatNamePromptLabel
|
||||
text: qsTr("Chat Name Prompt")
|
||||
helpText: qsTr("Prompt used to automatically generate chat names.")
|
||||
Layout.row: 11
|
||||
Layout.row: 14
|
||||
Layout.column: 0
|
||||
Layout.topMargin: 15
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: chatNamePrompt
|
||||
Layout.row: 12
|
||||
Layout.row: 15
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
@@ -297,14 +390,14 @@ MySettingsTab {
|
||||
id: suggestedFollowUpPromptLabel
|
||||
text: qsTr("Suggested FollowUp Prompt")
|
||||
helpText: qsTr("Prompt used to generate suggested follow-up questions.")
|
||||
Layout.row: 13
|
||||
Layout.row: 16
|
||||
Layout.column: 0
|
||||
Layout.topMargin: 15
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: suggestedFollowUpPrompt
|
||||
Layout.row: 14
|
||||
Layout.row: 17
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
@@ -337,7 +430,7 @@ MySettingsTab {
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.row: 15
|
||||
Layout.row: 18
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 2
|
||||
Layout.topMargin: 15
|
||||
@@ -833,7 +926,7 @@ MySettingsTab {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.row: 16
|
||||
Layout.row: 19
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 2
|
||||
Layout.topMargin: 15
|
||||
|
||||
@@ -34,6 +34,9 @@ Rectangle {
|
||||
ListElement {
|
||||
title: qsTr("LocalDocs")
|
||||
}
|
||||
ListElement {
|
||||
title: qsTr("Web Search")
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
@@ -152,6 +155,12 @@ Rectangle {
|
||||
Component { LocalDocsSettings { } }
|
||||
]
|
||||
}
|
||||
|
||||
MySettingsStack {
|
||||
tabs: [
|
||||
Component { WebSearchSettings { } }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
151
gpt4all-chat/qml/WebSearchSettings.qml
Normal file
151
gpt4all-chat/qml/WebSearchSettings.qml
Normal file
@@ -0,0 +1,151 @@
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
import localdocs
|
||||
import modellist
|
||||
import mysettings
|
||||
import network
|
||||
|
||||
MySettingsTab {
|
||||
onRestoreDefaultsClicked: {
|
||||
MySettings.restoreWebSearchDefaults();
|
||||
}
|
||||
|
||||
showRestoreDefaultsButton: true
|
||||
|
||||
title: qsTr("Web Search")
|
||||
contentItem: ColumnLayout {
|
||||
id: root
|
||||
spacing: 30
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
color: theme.grayRed900
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
font.bold: true
|
||||
text: qsTr("Web Search")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: theme.grayRed500
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
MySettingsLabel {
|
||||
id: usageModeLabel
|
||||
text: qsTr("Usage Mode")
|
||||
helpText: qsTr("When and how the brave search tool is executed.")
|
||||
}
|
||||
MyComboBox {
|
||||
id: usageModeBox
|
||||
Layout.minimumWidth: 400
|
||||
Layout.maximumWidth: 400
|
||||
Layout.alignment: Qt.AlignRight
|
||||
// NOTE: indices match values of UsageMode enum, keep them in sync
|
||||
model: ListModel {
|
||||
ListElement { name: qsTr("Never") }
|
||||
ListElement { name: qsTr("Model decides") }
|
||||
ListElement { name: qsTr("Force usage for every response where possible") }
|
||||
}
|
||||
function updateModel() {
|
||||
usageModeBox.currentIndex = MySettings.webSearchUsageMode;
|
||||
}
|
||||
Accessible.name: usageModeLabel.text
|
||||
Accessible.description: usageModeLabel.helpText
|
||||
onActivated: {
|
||||
MySettings.webSearchUsageMode = usageModeBox.currentIndex;
|
||||
}
|
||||
Component.onCompleted: {
|
||||
usageModeBox.updateModel();
|
||||
}
|
||||
Connections {
|
||||
target: MySettings
|
||||
function onWebSearchUsageModeChanged() {
|
||||
usageModeBox.updateModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
MySettingsLabel {
|
||||
id: apiKeyLabel
|
||||
text: qsTr("Brave AI API key")
|
||||
helpText: qsTr('The API key to use for Brave Web Search. Get one from the Brave for free <a href="https://brave.com/search/api/">API keys page</a>.')
|
||||
onLinkActivated: function(link) { Qt.openUrlExternally(link) }
|
||||
}
|
||||
|
||||
MyTextField {
|
||||
id: apiKeyField
|
||||
enabled: usageModeBox.currentIndex !== 0
|
||||
text: MySettings.braveSearchAPIKey
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.minimumWidth: 400
|
||||
Layout.maximumWidth: 400
|
||||
onEditingFinished: {
|
||||
MySettings.braveSearchAPIKey = apiKeyField.text;
|
||||
}
|
||||
Accessible.role: Accessible.EditableText
|
||||
Accessible.name: apiKeyLabel.text
|
||||
Accessible.description: apiKeyLabel.helpText
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
MySettingsLabel {
|
||||
id: contextItemsPerPrompt
|
||||
text: qsTr("Max source excerpts per prompt")
|
||||
helpText: qsTr("Max best N matches of retrieved source excerpts to add to the context for prompt. Larger numbers increase likelihood of factual responses, but also result in slower generation.")
|
||||
}
|
||||
|
||||
MyTextField {
|
||||
text: MySettings.webSearchRetrievalSize
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
validator: IntValidator {
|
||||
bottom: 1
|
||||
}
|
||||
onEditingFinished: {
|
||||
var val = parseInt(text)
|
||||
if (!isNaN(val)) {
|
||||
MySettings.webSearchRetrievalSize = val
|
||||
focus = false
|
||||
} else {
|
||||
text = MySettings.webSearchRetrievalSize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME:
|
||||
// RowLayout {
|
||||
// MySettingsLabel {
|
||||
// id: askBeforeRunningLabel
|
||||
// text: qsTr("Ask before running")
|
||||
// helpText: qsTr("The user is queried whether they want the tool to run in every instance.")
|
||||
// }
|
||||
// MyCheckBox {
|
||||
// id: askBeforeRunningBox
|
||||
// checked: MySettings.webSearchConfirmationMode
|
||||
// onClicked: {
|
||||
// MySettings.webSearchConfirmationMode = !MySettings.webSearchAskBeforeRunning
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
Rectangle {
|
||||
Layout.topMargin: 15
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: theme.settingsDivider
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,27 +56,13 @@ static inline QJsonObject modelToJson(const ModelInfo &info)
|
||||
return model;
|
||||
}
|
||||
|
||||
static inline QJsonObject resultToJson(const ResultInfo &info)
|
||||
{
|
||||
QJsonObject result;
|
||||
result.insert("file", info.file);
|
||||
result.insert("title", info.title);
|
||||
result.insert("author", info.author);
|
||||
result.insert("date", info.date);
|
||||
result.insert("text", info.text);
|
||||
result.insert("page", info.page);
|
||||
result.insert("from", info.from);
|
||||
result.insert("to", info.to);
|
||||
return result;
|
||||
}
|
||||
|
||||
Server::Server(Chat *chat)
|
||||
: ChatLLM(chat, true /*isServer*/)
|
||||
, m_chat(chat)
|
||||
, m_server(nullptr)
|
||||
{
|
||||
connect(this, &Server::threadStarted, this, &Server::start);
|
||||
connect(this, &Server::databaseResultsChanged, this, &Server::handleDatabaseResultsChanged);
|
||||
connect(this, &Server::sourceExcerptsChanged, this, &Server::handleSourceExcerptsChanged);
|
||||
connect(chat, &Chat::collectionListChanged, this, &Server::handleCollectionListChanged, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
@@ -373,7 +359,7 @@ QHttpServerResponse Server::handleCompletionRequest(const QHttpServerRequest &re
|
||||
|
||||
int promptTokens = 0;
|
||||
int responseTokens = 0;
|
||||
QList<QPair<QString, QList<ResultInfo>>> responses;
|
||||
QList<QPair<QString, QList<SourceExcerpt>>> responses;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (!promptInternal(
|
||||
m_collections,
|
||||
@@ -394,7 +380,7 @@ QHttpServerResponse Server::handleCompletionRequest(const QHttpServerRequest &re
|
||||
QString echoedPrompt = actualPrompt;
|
||||
if (!echoedPrompt.endsWith("\n"))
|
||||
echoedPrompt += "\n";
|
||||
responses.append(qMakePair((echo ? u"%1\n"_s.arg(actualPrompt) : QString()) + response(), m_databaseResults));
|
||||
responses.append(qMakePair((echo ? u"%1\n"_s.arg(actualPrompt) : QString()) + response(), m_sourceExcerpts));
|
||||
if (!promptTokens)
|
||||
promptTokens += m_promptTokens;
|
||||
responseTokens += m_promptResponseTokens - m_promptTokens;
|
||||
@@ -414,7 +400,7 @@ QHttpServerResponse Server::handleCompletionRequest(const QHttpServerRequest &re
|
||||
int index = 0;
|
||||
for (const auto &r : responses) {
|
||||
QString result = r.first;
|
||||
QList<ResultInfo> infos = r.second;
|
||||
QList<SourceExcerpt> infos = r.second;
|
||||
QJsonObject choice;
|
||||
choice.insert("index", index++);
|
||||
choice.insert("finish_reason", responseTokens == max_tokens ? "length" : "stop");
|
||||
@@ -422,30 +408,22 @@ QHttpServerResponse Server::handleCompletionRequest(const QHttpServerRequest &re
|
||||
message.insert("role", "assistant");
|
||||
message.insert("content", result);
|
||||
choice.insert("message", message);
|
||||
if (MySettings::globalInstance()->localDocsShowReferences()) {
|
||||
QJsonArray references;
|
||||
for (const auto &ref : infos)
|
||||
references.append(resultToJson(ref));
|
||||
choice.insert("references", references);
|
||||
}
|
||||
if (MySettings::globalInstance()->localDocsShowReferences())
|
||||
choice.insert("references", SourceExcerpt::toJson(infos));
|
||||
choices.append(choice);
|
||||
}
|
||||
} else {
|
||||
int index = 0;
|
||||
for (const auto &r : responses) {
|
||||
QString result = r.first;
|
||||
QList<ResultInfo> infos = r.second;
|
||||
QList<SourceExcerpt> infos = r.second;
|
||||
QJsonObject choice;
|
||||
choice.insert("text", result);
|
||||
choice.insert("index", index++);
|
||||
choice.insert("logprobs", QJsonValue::Null); // We don't support
|
||||
choice.insert("finish_reason", responseTokens == max_tokens ? "length" : "stop");
|
||||
if (MySettings::globalInstance()->localDocsShowReferences()) {
|
||||
QJsonArray references;
|
||||
for (const auto &ref : infos)
|
||||
references.append(resultToJson(ref));
|
||||
choice.insert("references", references);
|
||||
}
|
||||
if (MySettings::globalInstance()->localDocsShowReferences())
|
||||
choice.insert("references", SourceExcerpt::toJson(infos));
|
||||
choices.append(choice);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define SERVER_H
|
||||
|
||||
#include "chatllm.h"
|
||||
#include "database.h"
|
||||
#include "sourceexcerpt.h"
|
||||
|
||||
#include <QHttpServerRequest>
|
||||
#include <QHttpServerResponse>
|
||||
@@ -29,13 +29,13 @@ Q_SIGNALS:
|
||||
|
||||
private Q_SLOTS:
|
||||
QHttpServerResponse handleCompletionRequest(const QHttpServerRequest &request, bool isChat);
|
||||
void handleDatabaseResultsChanged(const QList<ResultInfo> &results) { m_databaseResults = results; }
|
||||
void handleSourceExcerptsChanged(const QList<SourceExcerpt> &sourceExcerpts) { m_sourceExcerpts = sourceExcerpts; }
|
||||
void handleCollectionListChanged(const QList<QString> &collectionList) { m_collections = collectionList; }
|
||||
|
||||
private:
|
||||
Chat *m_chat;
|
||||
QHttpServer *m_server;
|
||||
QList<ResultInfo> m_databaseResults;
|
||||
QList<SourceExcerpt> m_sourceExcerpts;
|
||||
QList<QString> m_collections;
|
||||
};
|
||||
|
||||
|
||||
131
gpt4all-chat/sourceexcerpt.cpp
Normal file
131
gpt4all-chat/sourceexcerpt.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include "sourceexcerpt.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
|
||||
QString SourceExcerpt::toJson(const QList<SourceExcerpt> &sources)
|
||||
{
|
||||
if (sources.isEmpty())
|
||||
return QString();
|
||||
|
||||
QJsonArray resultsArray;
|
||||
for (const auto &source : sources) {
|
||||
QJsonObject sourceObj;
|
||||
sourceObj["date"] = source.date;
|
||||
sourceObj["collection"] = source.collection;
|
||||
sourceObj["path"] = source.path;
|
||||
sourceObj["file"] = source.file;
|
||||
sourceObj["url"] = source.url;
|
||||
sourceObj["favicon"] = source.favicon;
|
||||
sourceObj["title"] = source.title;
|
||||
sourceObj["author"] = source.author;
|
||||
sourceObj["description"] = source.description;
|
||||
|
||||
QJsonArray excerptsArray;
|
||||
for (const auto &excerpt : source.excerpts) {
|
||||
QJsonObject excerptObj;
|
||||
excerptObj["text"] = excerpt.text;
|
||||
if (excerpt.page != -1)
|
||||
excerptObj["page"] = excerpt.page;
|
||||
if (excerpt.from != -1)
|
||||
excerptObj["from"] = excerpt.from;
|
||||
if (excerpt.to != -1)
|
||||
excerptObj["to"] = excerpt.to;
|
||||
excerptsArray.append(excerptObj);
|
||||
}
|
||||
sourceObj["excerpts"] = excerptsArray;
|
||||
|
||||
resultsArray.append(sourceObj);
|
||||
}
|
||||
|
||||
QJsonObject jsonObj;
|
||||
jsonObj["results"] = resultsArray;
|
||||
|
||||
QJsonDocument doc(jsonObj);
|
||||
return doc.toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
QList<SourceExcerpt> SourceExcerpt::fromJson(const QString &json, QString &errorString)
|
||||
{
|
||||
if (json.isEmpty())
|
||||
return QList<SourceExcerpt>();
|
||||
|
||||
QJsonParseError err;
|
||||
QJsonDocument document = QJsonDocument::fromJson(json.toUtf8(), &err);
|
||||
if (err.error != QJsonParseError::NoError) {
|
||||
errorString = err.errorString();
|
||||
return QList<SourceExcerpt>();
|
||||
}
|
||||
|
||||
QJsonObject jsonObject = document.object();
|
||||
Q_ASSERT(jsonObject.contains("results"));
|
||||
if (!jsonObject.contains("results")) {
|
||||
errorString = "json does not contain results array";
|
||||
return QList<SourceExcerpt>();
|
||||
}
|
||||
|
||||
QList<SourceExcerpt> excerpts;
|
||||
QJsonArray results = jsonObject["results"].toArray();
|
||||
for (int i = 0; i < results.size(); ++i) {
|
||||
QJsonObject result = results[i].toObject();
|
||||
if (!result.contains("date")) {
|
||||
errorString = "result does not contain required date field";
|
||||
return QList<SourceExcerpt>();
|
||||
}
|
||||
|
||||
if (!result.contains("excerpts") || !result["excerpts"].isArray()) {
|
||||
errorString = "result does not contain required excerpts array";
|
||||
return QList<SourceExcerpt>();
|
||||
}
|
||||
|
||||
QJsonArray textExcerpts = result["excerpts"].toArray();
|
||||
if (textExcerpts.isEmpty()) {
|
||||
errorString = "result excerpts array is empty";
|
||||
return QList<SourceExcerpt>();
|
||||
}
|
||||
|
||||
SourceExcerpt source;
|
||||
source.date = result["date"].toString();
|
||||
if (result.contains("collection"))
|
||||
source.collection = result["collection"].toString();
|
||||
if (result.contains("path"))
|
||||
source.path = result["path"].toString();
|
||||
if (result.contains("file"))
|
||||
source.file = result["file"].toString();
|
||||
if (result.contains("url"))
|
||||
source.url = result["url"].toString();
|
||||
if (result.contains("favicon"))
|
||||
source.favicon = result["favicon"].toString();
|
||||
if (result.contains("title"))
|
||||
source.title = result["title"].toString();
|
||||
if (result.contains("author"))
|
||||
source.author = result["author"].toString();
|
||||
if (result.contains("description"))
|
||||
source.author = result["description"].toString();
|
||||
|
||||
for (int i = 0; i < textExcerpts.size(); ++i) {
|
||||
if (!textExcerpts[i].isObject()) {
|
||||
errorString = "result excerpt is not an object";
|
||||
return QList<SourceExcerpt>();
|
||||
}
|
||||
QJsonObject excerptObj = textExcerpts[i].toObject();
|
||||
if (!excerptObj.contains("text")) {
|
||||
errorString = "result excerpt is does not have text field";
|
||||
return QList<SourceExcerpt>();
|
||||
}
|
||||
Excerpt excerpt;
|
||||
excerpt.text = excerptObj["text"].toString();
|
||||
if (excerptObj.contains("page"))
|
||||
excerpt.page = excerptObj["page"].toInt();
|
||||
if (excerptObj.contains("from"))
|
||||
excerpt.from = excerptObj["from"].toInt();
|
||||
if (excerptObj.contains("to"))
|
||||
excerpt.to = excerptObj["to"].toInt();
|
||||
source.excerpts.append(excerpt);
|
||||
}
|
||||
excerpts.append(source);
|
||||
}
|
||||
return excerpts;
|
||||
}
|
||||
99
gpt4all-chat/sourceexcerpt.h
Normal file
99
gpt4all-chat/sourceexcerpt.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#ifndef SOURCEEXCERT_H
|
||||
#define SOURCEEXCERT_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QFileInfo>
|
||||
#include <QUrl>
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
struct Excerpt {
|
||||
QString text; // [Required] The text actually used in the augmented context
|
||||
int page = -1; // [Optional] The page where the text was found
|
||||
int from = -1; // [Optional] The line number where the text begins
|
||||
int to = -1; // [Optional] The line number where the text ends
|
||||
bool operator==(const Excerpt &other) const {
|
||||
return text == other.text && page == other.page && from == other.from && to == other.to;
|
||||
}
|
||||
bool operator!=(const Excerpt &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
Q_DECLARE_METATYPE(Excerpt)
|
||||
|
||||
struct SourceExcerpt {
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QString date MEMBER date)
|
||||
Q_PROPERTY(QString collection MEMBER collection)
|
||||
Q_PROPERTY(QString path MEMBER path)
|
||||
Q_PROPERTY(QString file MEMBER file)
|
||||
Q_PROPERTY(QString url MEMBER url)
|
||||
Q_PROPERTY(QString favicon MEMBER favicon)
|
||||
Q_PROPERTY(QString title MEMBER title)
|
||||
Q_PROPERTY(QString author MEMBER author)
|
||||
Q_PROPERTY(QString description MEMBER description)
|
||||
Q_PROPERTY(QString fileUri READ fileUri STORED false)
|
||||
Q_PROPERTY(QString text READ text STORED false)
|
||||
Q_PROPERTY(QList<Excerpt> excerpts MEMBER excerpts)
|
||||
|
||||
public:
|
||||
QString date; // [Required] The creation or the last modification date whichever is latest
|
||||
QString collection; // [Optional] The name of the collection
|
||||
QString path; // [Optional] The full path
|
||||
QString file; // [Optional] The name of the file, but not the full path
|
||||
QString url; // [Optional] The name of the remote url
|
||||
QString favicon; // [Optional] The favicon
|
||||
QString title; // [Optional] The title of the document
|
||||
QString author; // [Optional] The author of the document
|
||||
QString description; // [Optional] The description of the source
|
||||
QList<Excerpt> excerpts;// [Required] The list of excerpts
|
||||
|
||||
// Returns a human readable string containing all the excerpts
|
||||
QString text() const {
|
||||
QStringList formattedExcerpts;
|
||||
for (const auto& excerpt : excerpts) {
|
||||
QString formattedExcerpt = excerpt.text;
|
||||
if (excerpt.page != -1) {
|
||||
formattedExcerpt += QStringLiteral(" (Page: %1").arg(excerpt.page);
|
||||
if (excerpt.from != -1 && excerpt.to != -1) {
|
||||
formattedExcerpt += QStringLiteral(", Lines: %1-%2").arg(excerpt.from).arg(excerpt.to);
|
||||
}
|
||||
formattedExcerpt += QStringLiteral(")");
|
||||
} else if (excerpt.from != -1 && excerpt.to != -1) {
|
||||
formattedExcerpt += QStringLiteral(" (Lines: %1-%2)").arg(excerpt.from).arg(excerpt.to);
|
||||
}
|
||||
formattedExcerpts.append(formattedExcerpt);
|
||||
}
|
||||
return formattedExcerpts.join(QStringLiteral("\n---\n"));
|
||||
}
|
||||
|
||||
QString fileUri() const {
|
||||
// QUrl reserved chars that are not UNSAFE_PATH according to glib/gconvert.c
|
||||
static const QByteArray s_exclude = "!$&'()*+,/:=@~"_ba;
|
||||
|
||||
Q_ASSERT(!QFileInfo(path).isRelative());
|
||||
#ifdef Q_OS_WINDOWS
|
||||
Q_ASSERT(!path.contains('\\')); // Qt normally uses forward slash as path separator
|
||||
#endif
|
||||
|
||||
auto escaped = QString::fromUtf8(QUrl::toPercentEncoding(path, s_exclude));
|
||||
if (escaped.front() != '/')
|
||||
escaped = '/' + escaped;
|
||||
return u"file://"_s + escaped;
|
||||
}
|
||||
|
||||
static QString toJson(const QList<SourceExcerpt> &sources);
|
||||
static QList<SourceExcerpt> fromJson(const QString &json, QString &errorString);
|
||||
|
||||
bool operator==(const SourceExcerpt &other) const {
|
||||
return file == other.file || url == other.url;
|
||||
}
|
||||
bool operator!=(const SourceExcerpt &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(SourceExcerpt)
|
||||
|
||||
#endif // SOURCEEXCERT_H
|
||||
1
gpt4all-chat/third_party/jinja2cpp
vendored
Submodule
1
gpt4all-chat/third_party/jinja2cpp
vendored
Submodule
Submodule gpt4all-chat/third_party/jinja2cpp added at e97a54e513
31
gpt4all-chat/tool.cpp
Normal file
31
gpt4all-chat/tool.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "tool.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
|
||||
QJsonObject filterModelGeneratedProperties(const QJsonObject &inputObject) {
|
||||
QJsonObject filteredObject;
|
||||
for (const QString &key : inputObject.keys()) {
|
||||
QJsonObject propertyObject = inputObject.value(key).toObject();
|
||||
if (!propertyObject.contains("modelGenerated") || propertyObject["modelGenerated"].toBool())
|
||||
filteredObject.insert(key, propertyObject);
|
||||
}
|
||||
return filteredObject;
|
||||
}
|
||||
|
||||
jinja2::Value Tool::jinjaValue() const
|
||||
{
|
||||
QJsonDocument doc(filterModelGeneratedProperties(paramSchema()));
|
||||
QString p(doc.toJson(QJsonDocument::Compact));
|
||||
|
||||
QJsonDocument exampleDoc(exampleParams());
|
||||
QString e(exampleDoc.toJson(QJsonDocument::Compact));
|
||||
|
||||
jinja2::ValuesMap params {
|
||||
{ "name", name().toStdString() },
|
||||
{ "description", description().toStdString() },
|
||||
{ "function", function().toStdString() },
|
||||
{ "paramSchema", p.toStdString() },
|
||||
{ "exampleParams", e.toStdString() }
|
||||
};
|
||||
return params;
|
||||
}
|
||||
116
gpt4all-chat/tool.h
Normal file
116
gpt4all-chat/tool.h
Normal file
@@ -0,0 +1,116 @@
|
||||
#ifndef TOOL_H
|
||||
#define TOOL_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <jinja2cpp/value.h>
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
namespace ToolEnums {
|
||||
Q_NAMESPACE
|
||||
enum class Error {
|
||||
NoError = 0,
|
||||
TimeoutError = 2,
|
||||
UnknownError = 499,
|
||||
};
|
||||
Q_ENUM_NS(Error)
|
||||
|
||||
enum class UsageMode {
|
||||
Disabled = 0, // Completely disabled
|
||||
Enabled = 1, // Enabled and the model decides whether to run
|
||||
ForceUsage = 2, // Attempt to force usage of the tool rather than let the LLM decide. NOTE: Not always possible.
|
||||
};
|
||||
Q_ENUM_NS(UsageMode)
|
||||
|
||||
enum class ConfirmationMode {
|
||||
NoConfirmation = 0, // No confirmation required
|
||||
AskBeforeRunning = 1, // User is queried on every execution
|
||||
AskBeforeRunningRecursive = 2, // User is queried if the tool is invoked in a recursive tool call
|
||||
};
|
||||
Q_ENUM_NS(ConfirmationMode)
|
||||
|
||||
// Ordered in increasing levels of privacy
|
||||
enum class PrivacyScope {
|
||||
None = 0, // Tool call data does not have any privacy scope
|
||||
LocalOrg = 1, // Tool call data does not leave the local organization
|
||||
Local = 2 // Tool call data does not leave the machine
|
||||
};
|
||||
Q_ENUM_NS(PrivacyScope)
|
||||
}
|
||||
|
||||
class Tool : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString name READ name CONSTANT)
|
||||
Q_PROPERTY(QString description READ description CONSTANT)
|
||||
Q_PROPERTY(QString function READ function CONSTANT)
|
||||
Q_PROPERTY(ToolEnums::PrivacyScope privacyScope READ privacyScope CONSTANT)
|
||||
Q_PROPERTY(QJsonObject paramSchema READ paramSchema CONSTANT)
|
||||
Q_PROPERTY(QJsonObject exampleParams READ exampleParams CONSTANT)
|
||||
Q_PROPERTY(QUrl url READ url CONSTANT)
|
||||
Q_PROPERTY(bool isBuiltin READ isBuiltin CONSTANT)
|
||||
Q_PROPERTY(ToolEnums::UsageMode usageMode READ usageMode NOTIFY usageModeChanged)
|
||||
Q_PROPERTY(ToolEnums::ConfirmationMode confirmationMode READ confirmationMode NOTIFY confirmationModeChanged)
|
||||
Q_PROPERTY(bool excerpts READ excerpts CONSTANT)
|
||||
|
||||
public:
|
||||
Tool() : QObject(nullptr) {}
|
||||
virtual ~Tool() {}
|
||||
|
||||
virtual QString run(const QJsonObject ¶meters, qint64 timeout = 2000) = 0;
|
||||
virtual ToolEnums::Error error() const { return ToolEnums::Error::NoError; }
|
||||
virtual QString errorString() const { return QString(); }
|
||||
|
||||
// [Required] Human readable name of the tool.
|
||||
virtual QString name() const = 0;
|
||||
|
||||
// [Required] Human readable description of the tool.
|
||||
virtual QString description() const = 0;
|
||||
|
||||
// [Required] Must be unique. Name of the function to invoke. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
|
||||
virtual QString function() const = 0;
|
||||
|
||||
// [Required] The privacy scope.
|
||||
virtual ToolEnums::PrivacyScope privacyScope() const = 0;
|
||||
|
||||
// [Optional] Json schema describing the tool's parameters. An empty object specifies no parameters.
|
||||
// https://json-schema.org/understanding-json-schema/
|
||||
// https://platform.openai.com/docs/api-reference/runs/createRun#runs-createrun-tools
|
||||
// https://github.com/ollama/ollama/blob/main/docs/api.md#chat-request-with-tools
|
||||
// FIXME: This should be validated against json schema
|
||||
virtual QJsonObject paramSchema() const { return QJsonObject(); }
|
||||
|
||||
// [Optional] An example of the parameters for this tool call. NOTE: This should only include parameters
|
||||
// that the model is responsible for generating.
|
||||
virtual QJsonObject exampleParams() const { return QJsonObject(); }
|
||||
|
||||
// [Optional] The local file or remote resource use to invoke the tool.
|
||||
virtual QUrl url() const { return QUrl(); }
|
||||
|
||||
// [Optional] Whether the tool is built-in.
|
||||
virtual bool isBuiltin() const { return false; }
|
||||
|
||||
// [Optional] The usage mode.
|
||||
virtual ToolEnums::UsageMode usageMode() const { return ToolEnums::UsageMode::Disabled; }
|
||||
|
||||
// [Optional] The confirmation mode.
|
||||
virtual ToolEnums::ConfirmationMode confirmationMode() const { return ToolEnums::ConfirmationMode::NoConfirmation; }
|
||||
|
||||
// [Optional] Whether json result produces source excerpts.
|
||||
virtual bool excerpts() const { return false; }
|
||||
|
||||
bool operator==(const Tool &other) const {
|
||||
return function() == other.function();
|
||||
}
|
||||
bool operator!=(const Tool &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
jinja2::Value jinjaValue() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void usageModeChanged();
|
||||
void confirmationModeChanged();
|
||||
};
|
||||
|
||||
#endif // TOOL_H
|
||||
47
gpt4all-chat/toolmodel.cpp
Normal file
47
gpt4all-chat/toolmodel.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "toolmodel.h"
|
||||
|
||||
#include <QGlobalStatic>
|
||||
#include <QGuiApplication>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "bravesearch.h"
|
||||
#include "localdocssearch.h"
|
||||
|
||||
class MyToolModel: public ToolModel { };
|
||||
Q_GLOBAL_STATIC(MyToolModel, toolModelInstance)
|
||||
ToolModel *ToolModel::globalInstance()
|
||||
{
|
||||
return toolModelInstance();
|
||||
}
|
||||
|
||||
ToolModel::ToolModel()
|
||||
: QAbstractListModel(nullptr) {
|
||||
|
||||
QCoreApplication::instance()->installEventFilter(this);
|
||||
|
||||
Tool* localDocsSearch = new LocalDocsSearch;
|
||||
m_tools.append(localDocsSearch);
|
||||
m_toolMap.insert(localDocsSearch->function(), localDocsSearch);
|
||||
connect(localDocsSearch, &Tool::usageModeChanged, this, &ToolModel::privacyScopeChanged);
|
||||
|
||||
Tool *braveSearch = new BraveSearch;
|
||||
m_tools.append(braveSearch);
|
||||
m_toolMap.insert(braveSearch->function(), braveSearch);
|
||||
connect(braveSearch, &Tool::usageModeChanged, this, &ToolModel::privacyScopeChanged);
|
||||
}
|
||||
|
||||
bool ToolModel::eventFilter(QObject *obj, QEvent *ev)
|
||||
{
|
||||
if (obj == QCoreApplication::instance() && ev->type() == QEvent::LanguageChange)
|
||||
emit dataChanged(index(0, 0), index(m_tools.size() - 1, 0));
|
||||
return false;
|
||||
}
|
||||
|
||||
ToolEnums::PrivacyScope ToolModel::privacyScope() const
|
||||
{
|
||||
ToolEnums::PrivacyScope scope = ToolEnums::PrivacyScope::Local; // highest scope
|
||||
for (const Tool *t : m_tools)
|
||||
if (t->usageMode() != ToolEnums::UsageMode::Disabled)
|
||||
scope = std::min(scope, t->privacyScope());
|
||||
return scope;
|
||||
}
|
||||
121
gpt4all-chat/toolmodel.h
Normal file
121
gpt4all-chat/toolmodel.h
Normal file
@@ -0,0 +1,121 @@
|
||||
#ifndef TOOLMODEL_H
|
||||
#define TOOLMODEL_H
|
||||
|
||||
#include "tool.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
class ToolModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int count READ count NOTIFY countChanged)
|
||||
Q_PROPERTY(ToolEnums::PrivacyScope privacyScope READ privacyScope NOTIFY privacyScopeChanged)
|
||||
|
||||
public:
|
||||
static ToolModel *globalInstance();
|
||||
|
||||
enum Roles {
|
||||
NameRole = Qt::UserRole + 1,
|
||||
DescriptionRole,
|
||||
FunctionRole,
|
||||
PrivacyScopeRole,
|
||||
ParametersRole,
|
||||
UrlRole,
|
||||
ApiKeyRole,
|
||||
KeyRequiredRole,
|
||||
IsBuiltinRole,
|
||||
UsageModeRole,
|
||||
ConfirmationModeRole,
|
||||
ExcerptsRole,
|
||||
};
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_tools.size();
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= m_tools.size())
|
||||
return QVariant();
|
||||
|
||||
const Tool *item = m_tools.at(index.row());
|
||||
switch (role) {
|
||||
case NameRole:
|
||||
return item->name();
|
||||
case DescriptionRole:
|
||||
return item->description();
|
||||
case FunctionRole:
|
||||
return item->function();
|
||||
case PrivacyScopeRole:
|
||||
return QVariant::fromValue(item->privacyScope());
|
||||
case ParametersRole:
|
||||
return item->paramSchema();
|
||||
case UrlRole:
|
||||
return item->url();
|
||||
case IsBuiltinRole:
|
||||
return item->isBuiltin();
|
||||
case UsageModeRole:
|
||||
return QVariant::fromValue(item->usageMode());
|
||||
case ConfirmationModeRole:
|
||||
return QVariant::fromValue(item->confirmationMode());
|
||||
case ExcerptsRole:
|
||||
return item->excerpts();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[NameRole] = "name";
|
||||
roles[DescriptionRole] = "description";
|
||||
roles[FunctionRole] = "function";
|
||||
roles[PrivacyScopeRole] = "privacyScope";
|
||||
roles[ParametersRole] = "parameters";
|
||||
roles[UrlRole] = "url";
|
||||
roles[ApiKeyRole] = "apiKey";
|
||||
roles[KeyRequiredRole] = "keyRequired";
|
||||
roles[IsBuiltinRole] = "isBuiltin";
|
||||
roles[UsageModeRole] = "usageMode";
|
||||
roles[ConfirmationModeRole] = "confirmationMode";
|
||||
roles[ExcerptsRole] = "excerpts";
|
||||
return roles;
|
||||
}
|
||||
|
||||
Q_INVOKABLE Tool* get(int index) const
|
||||
{
|
||||
if (index < 0 || index >= m_tools.size()) return nullptr;
|
||||
return m_tools.at(index);
|
||||
}
|
||||
|
||||
Q_INVOKABLE Tool *get(const QString &id) const
|
||||
{
|
||||
if (!m_toolMap.contains(id)) return nullptr;
|
||||
return m_toolMap.value(id);
|
||||
}
|
||||
|
||||
int count() const { return m_tools.size(); }
|
||||
|
||||
// Returns the least private scope of all enabled tools
|
||||
ToolEnums::PrivacyScope privacyScope() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void countChanged();
|
||||
void privacyScopeChanged();
|
||||
void valueChanged(int index, const QString &value);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *ev) override;
|
||||
|
||||
private:
|
||||
explicit ToolModel();
|
||||
~ToolModel() {}
|
||||
friend class MyToolModel;
|
||||
QList<Tool*> m_tools;
|
||||
QHash<QString, Tool*> m_toolMap;
|
||||
};
|
||||
|
||||
#endif // TOOLMODEL_H
|
||||
Reference in New Issue
Block a user