mirror of
https://github.com/nomic-ai/gpt4all.git
synced 2025-06-25 00:03:34 -04:00
Compare commits
2 Commits
main
...
v3.1.0-web
Author | SHA1 | Date | |
---|---|---|---|
|
8ee97ec26b | ||
|
1bafbaa846 |
@ -20,7 +20,7 @@ set(APP_VERSION_MAJOR 3)
|
|||||||
set(APP_VERSION_MINOR 1)
|
set(APP_VERSION_MINOR 1)
|
||||||
set(APP_VERSION_PATCH 0)
|
set(APP_VERSION_PATCH 0)
|
||||||
set(APP_VERSION_BASE "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}")
|
set(APP_VERSION_BASE "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}")
|
||||||
set(APP_VERSION "${APP_VERSION_BASE}-dev0")
|
set(APP_VERSION "${APP_VERSION_BASE}-web_search_beta")
|
||||||
|
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/Modules")
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/Modules")
|
||||||
|
|
||||||
@ -109,6 +109,7 @@ endif()
|
|||||||
|
|
||||||
qt_add_executable(chat
|
qt_add_executable(chat
|
||||||
main.cpp
|
main.cpp
|
||||||
|
bravesearch.h bravesearch.cpp
|
||||||
chat.h chat.cpp
|
chat.h chat.cpp
|
||||||
chatllm.h chatllm.cpp
|
chatllm.h chatllm.cpp
|
||||||
chatmodel.h chatlistmodel.h chatlistmodel.cpp
|
chatmodel.h chatlistmodel.h chatlistmodel.cpp
|
||||||
@ -122,6 +123,7 @@ qt_add_executable(chat
|
|||||||
modellist.h modellist.cpp
|
modellist.h modellist.cpp
|
||||||
mysettings.h mysettings.cpp
|
mysettings.h mysettings.cpp
|
||||||
network.h network.cpp
|
network.h network.cpp
|
||||||
|
sourceexcerpt.h
|
||||||
server.h server.cpp
|
server.h server.cpp
|
||||||
logger.h logger.cpp
|
logger.h logger.cpp
|
||||||
${APP_ICON_RESOURCE}
|
${APP_ICON_RESOURCE}
|
||||||
@ -155,6 +157,7 @@ qt_add_qml_module(chat
|
|||||||
qml/ThumbsDownDialog.qml
|
qml/ThumbsDownDialog.qml
|
||||||
qml/Toast.qml
|
qml/Toast.qml
|
||||||
qml/ToastManager.qml
|
qml/ToastManager.qml
|
||||||
|
qml/ToolSettings.qml
|
||||||
qml/MyBusyIndicator.qml
|
qml/MyBusyIndicator.qml
|
||||||
qml/MyButton.qml
|
qml/MyButton.qml
|
||||||
qml/MyCheckBox.qml
|
qml/MyCheckBox.qml
|
||||||
|
221
gpt4all-chat/bravesearch.cpp
Normal file
221
gpt4all-chat/bravesearch.cpp
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
#include "bravesearch.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;
|
||||||
|
|
||||||
|
QPair<QString, QList<SourceExcerpt>> BraveSearch::search(const QString &apiKey, const QString &query, int topK, unsigned long timeout)
|
||||||
|
{
|
||||||
|
QThread workerThread;
|
||||||
|
BraveAPIWorker worker;
|
||||||
|
worker.moveToThread(&workerThread);
|
||||||
|
connect(&worker, &BraveAPIWorker::finished, &workerThread, &QThread::quit, Qt::DirectConnection);
|
||||||
|
connect(this, &BraveSearch::request, &worker, &BraveAPIWorker::request, Qt::QueuedConnection);
|
||||||
|
workerThread.start();
|
||||||
|
emit request(apiKey, query, topK);
|
||||||
|
workerThread.wait(timeout);
|
||||||
|
workerThread.quit();
|
||||||
|
workerThread.wait();
|
||||||
|
return worker.response();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BraveAPIWorker::request(const QString &apiKey, const QString &query, int topK)
|
||||||
|
{
|
||||||
|
m_topK = topK;
|
||||||
|
QUrl jsonUrl("https://api.search.brave.com/res/v1/web/search");
|
||||||
|
QUrlQuery urlQuery;
|
||||||
|
urlQuery.addQueryItem("q", query);
|
||||||
|
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-Encoding", "gzip");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QPair<QString, QList<SourceExcerpt>> cleanBraveResponse(const QByteArray& jsonResponse, qsizetype topK = 1)
|
||||||
|
{
|
||||||
|
QJsonParseError err;
|
||||||
|
QJsonDocument document = QJsonDocument::fromJson(jsonResponse, &err);
|
||||||
|
if (err.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "ERROR: Couldn't parse: " << jsonResponse << err.errorString();
|
||||||
|
return QPair<QString, QList<SourceExcerpt>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject searchResponse = document.object();
|
||||||
|
QJsonObject cleanResponse;
|
||||||
|
QString query;
|
||||||
|
QJsonArray cleanArray;
|
||||||
|
|
||||||
|
QList<SourceExcerpt> infos;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
for (int i = 0; i < std::min(mainResults.size(), topK); ++i) {
|
||||||
|
QJsonObject m = mainResults[i].toObject();
|
||||||
|
QString r_type = m["type"].toString();
|
||||||
|
int idx = m["index"].toInt();
|
||||||
|
QJsonObject resultsObject = searchResponse[r_type].toObject();
|
||||||
|
QJsonArray resultsArray = resultsObject["results"].toArray();
|
||||||
|
|
||||||
|
QJsonValue cleaned;
|
||||||
|
SourceExcerpt info;
|
||||||
|
if (r_type == "web") {
|
||||||
|
// For web data - add a single output from the search
|
||||||
|
QJsonObject resultObj = resultsArray[idx].toObject();
|
||||||
|
QStringList selectedKeys = {"type", "title", "url", "description", "date", "extra_snippets"};
|
||||||
|
QJsonObject cleanedObj;
|
||||||
|
for (const auto& key : selectedKeys) {
|
||||||
|
if (resultObj.contains(key)) {
|
||||||
|
cleanedObj.insert(key, resultObj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info.date = resultObj["date"].toString();
|
||||||
|
info.text = resultObj["description"].toString(); // fixme
|
||||||
|
info.url = resultObj["url"].toString();
|
||||||
|
QJsonObject meta_url = resultObj["meta_url"].toObject();
|
||||||
|
info.favicon = meta_url["favicon"].toString();
|
||||||
|
info.title = resultObj["title"].toString();
|
||||||
|
|
||||||
|
cleaned = cleanedObj;
|
||||||
|
} else if (r_type == "faq") {
|
||||||
|
// For faq data - take a list of all the questions & answers
|
||||||
|
QStringList selectedKeys = {"type", "question", "answer", "title", "url"};
|
||||||
|
QJsonArray cleanedArray;
|
||||||
|
for (const auto& q : resultsArray) {
|
||||||
|
QJsonObject qObj = q.toObject();
|
||||||
|
QJsonObject cleanedObj;
|
||||||
|
for (const auto& key : selectedKeys) {
|
||||||
|
if (qObj.contains(key)) {
|
||||||
|
cleanedObj.insert(key, qObj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cleanedArray.append(cleanedObj);
|
||||||
|
}
|
||||||
|
cleaned = cleanedArray;
|
||||||
|
} else if (r_type == "infobox") {
|
||||||
|
QJsonObject resultObj = resultsArray[idx].toObject();
|
||||||
|
QStringList selectedKeys = {"type", "title", "url", "description", "long_desc"};
|
||||||
|
QJsonObject cleanedObj;
|
||||||
|
for (const auto& key : selectedKeys) {
|
||||||
|
if (resultObj.contains(key)) {
|
||||||
|
cleanedObj.insert(key, resultObj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cleaned = cleanedObj;
|
||||||
|
} else if (r_type == "videos") {
|
||||||
|
QStringList selectedKeys = {"type", "url", "title", "description", "date"};
|
||||||
|
QJsonArray cleanedArray;
|
||||||
|
for (const auto& q : resultsArray) {
|
||||||
|
QJsonObject qObj = q.toObject();
|
||||||
|
QJsonObject cleanedObj;
|
||||||
|
for (const auto& key : selectedKeys) {
|
||||||
|
if (qObj.contains(key)) {
|
||||||
|
cleanedObj.insert(key, qObj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cleanedArray.append(cleanedObj);
|
||||||
|
}
|
||||||
|
cleaned = cleanedArray;
|
||||||
|
} else if (r_type == "locations") {
|
||||||
|
QStringList selectedKeys = {"type", "title", "url", "description", "coordinates", "postal_address", "contact", "rating", "distance", "zoom_level"};
|
||||||
|
QJsonArray cleanedArray;
|
||||||
|
for (const auto& q : resultsArray) {
|
||||||
|
QJsonObject qObj = q.toObject();
|
||||||
|
QJsonObject cleanedObj;
|
||||||
|
for (const auto& key : selectedKeys) {
|
||||||
|
if (qObj.contains(key)) {
|
||||||
|
cleanedObj.insert(key, qObj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cleanedArray.append(cleanedObj);
|
||||||
|
}
|
||||||
|
cleaned = cleanedArray;
|
||||||
|
} else if (r_type == "news") {
|
||||||
|
QStringList selectedKeys = {"type", "title", "url", "description"};
|
||||||
|
QJsonArray cleanedArray;
|
||||||
|
for (const auto& q : resultsArray) {
|
||||||
|
QJsonObject qObj = q.toObject();
|
||||||
|
QJsonObject cleanedObj;
|
||||||
|
for (const auto& key : selectedKeys) {
|
||||||
|
if (qObj.contains(key)) {
|
||||||
|
cleanedObj.insert(key, qObj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cleanedArray.append(cleanedObj);
|
||||||
|
}
|
||||||
|
cleaned = cleanedArray;
|
||||||
|
} else {
|
||||||
|
cleaned = QJsonValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
infos.append(info);
|
||||||
|
cleanArray.append(cleaned);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanResponse.insert("query", query);
|
||||||
|
cleanResponse.insert("top_k", cleanArray);
|
||||||
|
QJsonDocument cleanedDoc(cleanResponse);
|
||||||
|
|
||||||
|
// qDebug().noquote() << document.toJson(QJsonDocument::Indented);
|
||||||
|
// qDebug().noquote() << cleanedDoc.toJson(QJsonDocument::Indented);
|
||||||
|
|
||||||
|
return qMakePair(cleanedDoc.toJson(QJsonDocument::Indented), infos);
|
||||||
|
}
|
||||||
|
|
||||||
|
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, m_topK);
|
||||||
|
} else {
|
||||||
|
QByteArray jsonData = jsonReply->readAll();
|
||||||
|
qWarning() << "ERROR: Could not search brave" << jsonReply->error() << jsonReply->errorString() << jsonData;
|
||||||
|
jsonReply->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BraveAPIWorker::handleErrorOccurred(QNetworkReply::NetworkError code)
|
||||||
|
{
|
||||||
|
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
||||||
|
Q_ASSERT(reply);
|
||||||
|
qWarning().noquote() << "ERROR: BraveAPIWorker::handleErrorOccurred got HTTP Error" << code << "response:"
|
||||||
|
<< reply->errorString();
|
||||||
|
emit finished();
|
||||||
|
}
|
51
gpt4all-chat/bravesearch.h
Normal file
51
gpt4all-chat/bravesearch.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#ifndef BRAVESEARCH_H
|
||||||
|
#define BRAVESEARCH_H
|
||||||
|
|
||||||
|
#include "sourceexcerpt.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
class BraveAPIWorker : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
BraveAPIWorker()
|
||||||
|
: QObject(nullptr)
|
||||||
|
, m_networkManager(nullptr)
|
||||||
|
, m_topK(1) {}
|
||||||
|
virtual ~BraveAPIWorker() {}
|
||||||
|
|
||||||
|
QPair<QString, QList<SourceExcerpt>> response() const { return m_response; }
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void request(const QString &apiKey, const QString &query, int topK);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void finished();
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void handleFinished();
|
||||||
|
void handleErrorOccurred(QNetworkReply::NetworkError code);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QNetworkAccessManager *m_networkManager;
|
||||||
|
QPair<QString, QList<SourceExcerpt>> m_response;
|
||||||
|
int m_topK;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BraveSearch : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
BraveSearch()
|
||||||
|
: QObject(nullptr) {}
|
||||||
|
virtual ~BraveSearch() {}
|
||||||
|
|
||||||
|
QPair<QString, QList<SourceExcerpt>> search(const QString &apiKey, const QString &query, int topK, unsigned long timeout = 2000);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void request(const QString &apiKey, const QString &query, int topK);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BRAVESEARCH_H
|
@ -59,6 +59,7 @@ void Chat::connectLLM()
|
|||||||
connect(m_llmodel, &ChatLLM::responseChanged, this, &Chat::handleResponseChanged, Qt::QueuedConnection);
|
connect(m_llmodel, &ChatLLM::responseChanged, this, &Chat::handleResponseChanged, Qt::QueuedConnection);
|
||||||
connect(m_llmodel, &ChatLLM::promptProcessing, this, &Chat::promptProcessing, 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::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::responseStopped, this, &Chat::responseStopped, Qt::QueuedConnection);
|
||||||
connect(m_llmodel, &ChatLLM::modelLoadingError, this, &Chat::handleModelLoadingError, Qt::QueuedConnection);
|
connect(m_llmodel, &ChatLLM::modelLoadingError, this, &Chat::handleModelLoadingError, Qt::QueuedConnection);
|
||||||
connect(m_llmodel, &ChatLLM::modelLoadingWarning, this, &Chat::modelLoadingWarning, Qt::QueuedConnection);
|
connect(m_llmodel, &ChatLLM::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::generatedQuestionFinished, this, &Chat::generatedQuestionFinished, Qt::QueuedConnection);
|
||||||
connect(m_llmodel, &ChatLLM::reportSpeed, this, &Chat::handleTokenSpeedChanged, 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::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::modelInfoChanged, this, &Chat::handleModelInfoChanged, Qt::QueuedConnection);
|
||||||
connect(m_llmodel, &ChatLLM::trySwitchContextOfLoadedModelCompleted, this, &Chat::handleTrySwitchContextOfLoadedModelCompleted, Qt::QueuedConnection);
|
connect(m_llmodel, &ChatLLM::trySwitchContextOfLoadedModelCompleted, this, &Chat::handleTrySwitchContextOfLoadedModelCompleted, Qt::QueuedConnection);
|
||||||
|
|
||||||
@ -121,6 +122,7 @@ void Chat::resetResponseState()
|
|||||||
emit tokenSpeedChanged();
|
emit tokenSpeedChanged();
|
||||||
m_responseInProgress = true;
|
m_responseInProgress = true;
|
||||||
m_responseState = m_collections.empty() ? Chat::PromptProcessing : Chat::LocalDocsRetrieval;
|
m_responseState = m_collections.empty() ? Chat::PromptProcessing : Chat::LocalDocsRetrieval;
|
||||||
|
m_toolDescription = QString();
|
||||||
emit responseInProgressChanged();
|
emit responseInProgressChanged();
|
||||||
emit responseStateChanged();
|
emit responseStateChanged();
|
||||||
}
|
}
|
||||||
@ -134,7 +136,7 @@ void Chat::prompt(const QString &prompt)
|
|||||||
void Chat::regenerateResponse()
|
void Chat::regenerateResponse()
|
||||||
{
|
{
|
||||||
const int index = m_chatModel->count() - 1;
|
const int index = m_chatModel->count() - 1;
|
||||||
m_chatModel->updateSources(index, QList<ResultInfo>());
|
m_chatModel->updateSources(index, QList<SourceExcerpt>());
|
||||||
emit regenerateResponseRequested();
|
emit regenerateResponseRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,8 +191,13 @@ void Chat::handleModelLoadingPercentageChanged(float loadingPercentage)
|
|||||||
|
|
||||||
void Chat::promptProcessing()
|
void Chat::promptProcessing()
|
||||||
{
|
{
|
||||||
m_responseState = !databaseResults().isEmpty() ? Chat::LocalDocsProcessing : Chat::PromptProcessing;
|
if (sourceExcerpts().isEmpty())
|
||||||
emit responseStateChanged();
|
m_responseState = Chat::PromptProcessing;
|
||||||
|
else if (m_responseState == Chat::ToolCalled)
|
||||||
|
m_responseState = Chat::ToolProcessing;
|
||||||
|
else
|
||||||
|
m_responseState = Chat::LocalDocsProcessing;
|
||||||
|
emit responseStateChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chat::generatingQuestions()
|
void Chat::generatingQuestions()
|
||||||
@ -199,6 +206,14 @@ void Chat::generatingQuestions()
|
|||||||
emit responseStateChanged();
|
emit responseStateChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Chat::toolCalled(const QString &description)
|
||||||
|
{
|
||||||
|
m_responseState = Chat::ToolCalled;
|
||||||
|
m_toolDescription = description;
|
||||||
|
emit toolDescriptionChanged();
|
||||||
|
emit responseStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
void Chat::responseStopped(qint64 promptResponseMs)
|
void Chat::responseStopped(qint64 promptResponseMs)
|
||||||
{
|
{
|
||||||
m_tokenSpeed = QString();
|
m_tokenSpeed = QString();
|
||||||
@ -357,11 +372,11 @@ QString Chat::fallbackReason() const
|
|||||||
return m_llmodel->fallbackReason();
|
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;
|
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)
|
void Chat::handleModelInfoChanged(const ModelInfo &modelInfo)
|
||||||
|
@ -40,6 +40,7 @@ class Chat : public QObject
|
|||||||
// 0=no, 1=waiting, 2=working
|
// 0=no, 1=waiting, 2=working
|
||||||
Q_PROPERTY(int trySwitchContextInProgress READ trySwitchContextInProgress NOTIFY trySwitchContextInProgressChanged)
|
Q_PROPERTY(int trySwitchContextInProgress READ trySwitchContextInProgress NOTIFY trySwitchContextInProgressChanged)
|
||||||
Q_PROPERTY(QList<QString> generatedQuestions READ generatedQuestions NOTIFY generatedQuestionsChanged)
|
Q_PROPERTY(QList<QString> generatedQuestions READ generatedQuestions NOTIFY generatedQuestionsChanged)
|
||||||
|
Q_PROPERTY(QString toolDescription READ toolDescription NOTIFY toolDescriptionChanged)
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_UNCREATABLE("Only creatable from c++!")
|
QML_UNCREATABLE("Only creatable from c++!")
|
||||||
|
|
||||||
@ -50,7 +51,9 @@ public:
|
|||||||
LocalDocsProcessing,
|
LocalDocsProcessing,
|
||||||
PromptProcessing,
|
PromptProcessing,
|
||||||
GeneratingQuestions,
|
GeneratingQuestions,
|
||||||
ResponseGeneration
|
ResponseGeneration,
|
||||||
|
ToolCalled,
|
||||||
|
ToolProcessing
|
||||||
};
|
};
|
||||||
Q_ENUM(ResponseState)
|
Q_ENUM(ResponseState)
|
||||||
|
|
||||||
@ -81,9 +84,10 @@ public:
|
|||||||
Q_INVOKABLE void stopGenerating();
|
Q_INVOKABLE void stopGenerating();
|
||||||
Q_INVOKABLE void newPromptResponsePair(const QString &prompt);
|
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 response() const;
|
||||||
|
QString toolDescription() const { return m_toolDescription; }
|
||||||
bool responseInProgress() const { return m_responseInProgress; }
|
bool responseInProgress() const { return m_responseInProgress; }
|
||||||
ResponseState responseState() const;
|
ResponseState responseState() const;
|
||||||
ModelInfo modelInfo() const;
|
ModelInfo modelInfo() const;
|
||||||
@ -158,19 +162,21 @@ Q_SIGNALS:
|
|||||||
void trySwitchContextInProgressChanged();
|
void trySwitchContextInProgressChanged();
|
||||||
void loadedModelInfoChanged();
|
void loadedModelInfoChanged();
|
||||||
void generatedQuestionsChanged();
|
void generatedQuestionsChanged();
|
||||||
|
void toolDescriptionChanged();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void handleResponseChanged(const QString &response);
|
void handleResponseChanged(const QString &response);
|
||||||
void handleModelLoadingPercentageChanged(float);
|
void handleModelLoadingPercentageChanged(float);
|
||||||
void promptProcessing();
|
void promptProcessing();
|
||||||
void generatingQuestions();
|
void generatingQuestions();
|
||||||
|
void toolCalled(const QString &description);
|
||||||
void responseStopped(qint64 promptResponseMs);
|
void responseStopped(qint64 promptResponseMs);
|
||||||
void generatedNameChanged(const QString &name);
|
void generatedNameChanged(const QString &name);
|
||||||
void generatedQuestionFinished(const QString &question);
|
void generatedQuestionFinished(const QString &question);
|
||||||
void handleRecalculating();
|
void handleRecalculating();
|
||||||
void handleModelLoadingError(const QString &error);
|
void handleModelLoadingError(const QString &error);
|
||||||
void handleTokenSpeedChanged(const QString &tokenSpeed);
|
void handleTokenSpeedChanged(const QString &tokenSpeed);
|
||||||
void handleDatabaseResultsChanged(const QList<ResultInfo> &results);
|
void handleSourceExcerptsChanged(const QList<SourceExcerpt> &sourceExcerpts);
|
||||||
void handleModelInfoChanged(const ModelInfo &modelInfo);
|
void handleModelInfoChanged(const ModelInfo &modelInfo);
|
||||||
void handleTrySwitchContextOfLoadedModelCompleted(int value);
|
void handleTrySwitchContextOfLoadedModelCompleted(int value);
|
||||||
|
|
||||||
@ -185,6 +191,7 @@ private:
|
|||||||
QString m_device;
|
QString m_device;
|
||||||
QString m_fallbackReason;
|
QString m_fallbackReason;
|
||||||
QString m_response;
|
QString m_response;
|
||||||
|
QString m_toolDescription;
|
||||||
QList<QString> m_collections;
|
QList<QString> m_collections;
|
||||||
QList<QString> m_generatedQuestions;
|
QList<QString> m_generatedQuestions;
|
||||||
ChatModel *m_chatModel;
|
ChatModel *m_chatModel;
|
||||||
@ -192,7 +199,7 @@ private:
|
|||||||
ResponseState m_responseState;
|
ResponseState m_responseState;
|
||||||
qint64 m_creationDate;
|
qint64 m_creationDate;
|
||||||
ChatLLM *m_llmodel;
|
ChatLLM *m_llmodel;
|
||||||
QList<ResultInfo> m_databaseResults;
|
QList<SourceExcerpt> m_sourceExcerpts;
|
||||||
bool m_isServer = false;
|
bool m_isServer = false;
|
||||||
bool m_shouldDeleteLater = false;
|
bool m_shouldDeleteLater = false;
|
||||||
float m_modelLoadingPercentage = 0.0f;
|
float m_modelLoadingPercentage = 0.0f;
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#define CHAT_FORMAT_MAGIC 0xF5D553CC
|
#define CHAT_FORMAT_MAGIC 0xF5D553CC
|
||||||
#define CHAT_FORMAT_VERSION 9
|
#define CHAT_FORMAT_VERSION 10
|
||||||
|
|
||||||
class MyChatListModel: public ChatListModel { };
|
class MyChatListModel: public ChatListModel { };
|
||||||
Q_GLOBAL_STATIC(MyChatListModel, chatListModelInstance)
|
Q_GLOBAL_STATIC(MyChatListModel, chatListModelInstance)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "chatllm.h"
|
#include "chatllm.h"
|
||||||
|
|
||||||
|
#include "bravesearch.h"
|
||||||
#include "chat.h"
|
#include "chat.h"
|
||||||
#include "chatapi.h"
|
#include "chatapi.h"
|
||||||
#include "localdocs.h"
|
#include "localdocs.h"
|
||||||
@ -10,6 +11,7 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QGlobalStatic>
|
#include <QGlobalStatic>
|
||||||
|
#include <QGuiApplication>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
@ -113,6 +115,7 @@ ChatLLM::ChatLLM(Chat *parent, bool isServer)
|
|||||||
, m_reloadingToChangeVariant(false)
|
, m_reloadingToChangeVariant(false)
|
||||||
, m_processedSystemPrompt(false)
|
, m_processedSystemPrompt(false)
|
||||||
, m_restoreStateFromText(false)
|
, m_restoreStateFromText(false)
|
||||||
|
, m_maybeToolCall(false)
|
||||||
{
|
{
|
||||||
moveToThread(&m_llmThread);
|
moveToThread(&m_llmThread);
|
||||||
connect(this, &ChatLLM::shouldBeLoadedChanged, this, &ChatLLM::handleShouldBeLoadedChanged,
|
connect(this, &ChatLLM::shouldBeLoadedChanged, this, &ChatLLM::handleShouldBeLoadedChanged,
|
||||||
@ -701,13 +704,44 @@ bool ChatLLM::handleResponse(int32_t token, const std::string &response)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only valid for llama 3.1 instruct
|
||||||
|
if (m_modelInfo.filename().startsWith("Meta-Llama-3.1-8B-Instruct")) {
|
||||||
|
// Based on https://llama.meta.com/docs/model-cards-and-prompt-formats/llama3_1/#built-in-python-based-tool-calling
|
||||||
|
// For brave_search and wolfram_alpha ipython is always used
|
||||||
|
|
||||||
|
// <|python_tag|>
|
||||||
|
// brave_search.call(query="...")
|
||||||
|
// <|eom_id|>
|
||||||
|
const int eom_id = 128008;
|
||||||
|
const int python_tag = 128010;
|
||||||
|
|
||||||
|
// If we have a built-in tool call, then it should be the first token
|
||||||
|
const bool isFirstResponseToken = m_promptResponseTokens == m_promptTokens;
|
||||||
|
Q_ASSERT(token != python_tag || isFirstResponseToken);
|
||||||
|
if (isFirstResponseToken && token == python_tag) {
|
||||||
|
m_maybeToolCall = true;
|
||||||
|
++m_promptResponseTokens;
|
||||||
|
return !m_stopGenerating;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for end of built-in tool call
|
||||||
|
Q_ASSERT(token != eom_id || !m_maybeToolCall);
|
||||||
|
if (token == eom_id) {
|
||||||
|
++m_promptResponseTokens;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// m_promptResponseTokens is related to last prompt/response not
|
// m_promptResponseTokens is related to last prompt/response not
|
||||||
// the entire context window which we can reset on regenerate prompt
|
// the entire context window which we can reset on regenerate prompt
|
||||||
++m_promptResponseTokens;
|
++m_promptResponseTokens;
|
||||||
m_timer->inc();
|
m_timer->inc();
|
||||||
Q_ASSERT(!response.empty());
|
Q_ASSERT(!response.empty());
|
||||||
m_response.append(response);
|
m_response.append(response);
|
||||||
emit responseChanged(QString::fromStdString(remove_leading_whitespace(m_response)));
|
|
||||||
|
if (!m_maybeToolCall)
|
||||||
|
emit responseChanged(QString::fromStdString(remove_leading_whitespace(m_response)));
|
||||||
|
|
||||||
return !m_stopGenerating;
|
return !m_stopGenerating;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -745,24 +779,24 @@ bool ChatLLM::prompt(const QList<QString> &collectionList, const QString &prompt
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ChatLLM::promptInternal(const QList<QString> &collectionList, const QString &prompt, const QString &promptTemplate,
|
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 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)
|
int32_t repeat_penalty_tokens)
|
||||||
{
|
{
|
||||||
if (!isModelLoaded())
|
if (!isModelLoaded())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
QList<ResultInfo> databaseResults;
|
QList<SourceExcerpt> databaseResults;
|
||||||
const int retrievalSize = MySettings::globalInstance()->localDocsRetrievalSize();
|
const int retrievalSize = MySettings::globalInstance()->localDocsRetrievalSize();
|
||||||
if (!collectionList.isEmpty()) {
|
if (!collectionList.isEmpty()) {
|
||||||
emit requestRetrieveFromDB(collectionList, prompt, retrievalSize, &databaseResults); // blocks
|
emit requestRetrieveFromDB(collectionList, prompt, retrievalSize, &databaseResults); // blocks
|
||||||
emit databaseResultsChanged(databaseResults);
|
emit sourceExcerptsChanged(databaseResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Augment the prompt template with the results if any
|
// Augment the prompt template with the results if any
|
||||||
QString docsContext;
|
QString docsContext;
|
||||||
if (!databaseResults.isEmpty()) {
|
if (!databaseResults.isEmpty()) {
|
||||||
QStringList results;
|
QStringList results;
|
||||||
for (const ResultInfo &info : databaseResults)
|
for (const SourceExcerpt &info : databaseResults)
|
||||||
results << u"Collection: %1\nPath: %2\nExcerpt: %3"_s.arg(info.collection, info.path, info.text);
|
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
|
// FIXME(jared): use a Jinja prompt template instead of hardcoded Alpaca-style localdocs template
|
||||||
@ -806,21 +840,66 @@ bool ChatLLM::promptInternal(const QList<QString> &collectionList, const QString
|
|||||||
m_timer->stop();
|
m_timer->stop();
|
||||||
qint64 elapsed = totalTime.elapsed();
|
qint64 elapsed = totalTime.elapsed();
|
||||||
std::string trimmed = trim_whitespace(m_response);
|
std::string trimmed = trim_whitespace(m_response);
|
||||||
if (trimmed != m_response) {
|
if (m_maybeToolCall) {
|
||||||
m_response = trimmed;
|
m_maybeToolCall = false;
|
||||||
emit responseChanged(QString::fromStdString(m_response));
|
m_ctx.n_past = std::max(0, m_ctx.n_past);
|
||||||
}
|
m_ctx.tokens.erase(m_ctx.tokens.end() - m_promptResponseTokens, m_ctx.tokens.end());
|
||||||
|
m_promptResponseTokens = 0;
|
||||||
|
m_promptTokens = 0;
|
||||||
|
m_response = std::string();
|
||||||
|
return toolCallInternal(QString::fromStdString(trimmed), n_predict, top_k, top_p, min_p, temp,
|
||||||
|
n_batch, repeat_penalty, repeat_penalty_tokens);
|
||||||
|
} else {
|
||||||
|
if (trimmed != m_response) {
|
||||||
|
m_response = trimmed;
|
||||||
|
emit responseChanged(QString::fromStdString(m_response));
|
||||||
|
}
|
||||||
|
|
||||||
SuggestionMode mode = MySettings::globalInstance()->suggestionMode();
|
SuggestionMode mode = MySettings::globalInstance()->suggestionMode();
|
||||||
if (mode == SuggestionMode::On || (!databaseResults.isEmpty() && mode == SuggestionMode::LocalDocsOnly))
|
if (mode == SuggestionMode::On || (!databaseResults.isEmpty() && mode == SuggestionMode::LocalDocsOnly))
|
||||||
generateQuestions(elapsed);
|
generateQuestions(elapsed);
|
||||||
else
|
else
|
||||||
emit responseStopped(elapsed);
|
emit responseStopped(elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
m_pristineLoadedState = false;
|
m_pristineLoadedState = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ChatLLM::toolCallInternal(const QString &toolCall, 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)
|
||||||
|
{
|
||||||
|
Q_ASSERT(m_modelInfo.filename().startsWith("Meta-Llama-3.1-8B-Instruct"));
|
||||||
|
emit toolCalled(tr("searching web..."));
|
||||||
|
|
||||||
|
// Based on https://llama.meta.com/docs/model-cards-and-prompt-formats/llama3_1/#built-in-python-based-tool-calling
|
||||||
|
// For brave_search and wolfram_alpha ipython is always used
|
||||||
|
|
||||||
|
static QRegularExpression re(R"(brave_search\.call\(query=\"([^\"]+)\"\))");
|
||||||
|
QRegularExpressionMatch match = re.match(toolCall);
|
||||||
|
|
||||||
|
QString prompt("<|start_header_id|>ipython<|end_header_id|>\n\n%1<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n%2");
|
||||||
|
QString query;
|
||||||
|
if (match.hasMatch()) {
|
||||||
|
query = match.captured(1);
|
||||||
|
} else {
|
||||||
|
qWarning() << "WARNING: Could not find the tool for " << toolCall;
|
||||||
|
return promptInternal(QList<QString>()/*collectionList*/, prompt.arg(QString()), QString("%1") /*promptTemplate*/,
|
||||||
|
n_predict, top_k, top_p, min_p, temp, n_batch, repeat_penalty, repeat_penalty_tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString apiKey = MySettings::globalInstance()->braveSearchAPIKey();
|
||||||
|
Q_ASSERT(apiKey != "");
|
||||||
|
|
||||||
|
BraveSearch brave;
|
||||||
|
const QPair<QString, QList<SourceExcerpt>> braveResponse = brave.search(apiKey, query, 2 /*topK*/, 2000 /*msecs to timeout*/);
|
||||||
|
|
||||||
|
emit sourceExcerptsChanged(braveResponse.second);
|
||||||
|
|
||||||
|
return promptInternal(QList<QString>()/*collectionList*/, prompt.arg(braveResponse.first), QString("%1") /*promptTemplate*/,
|
||||||
|
n_predict, top_k, top_p, min_p, temp, n_batch, repeat_penalty, repeat_penalty_tokens);
|
||||||
|
}
|
||||||
|
|
||||||
void ChatLLM::setShouldBeLoaded(bool b)
|
void ChatLLM::setShouldBeLoaded(bool b)
|
||||||
{
|
{
|
||||||
#if defined(DEBUG_MODEL_LOADING)
|
#if defined(DEBUG_MODEL_LOADING)
|
||||||
|
@ -180,6 +180,7 @@ Q_SIGNALS:
|
|||||||
void responseChanged(const QString &response);
|
void responseChanged(const QString &response);
|
||||||
void promptProcessing();
|
void promptProcessing();
|
||||||
void generatingQuestions();
|
void generatingQuestions();
|
||||||
|
void toolCalled(const QString &description);
|
||||||
void responseStopped(qint64 promptResponseMs);
|
void responseStopped(qint64 promptResponseMs);
|
||||||
void generatedNameChanged(const QString &name);
|
void generatedNameChanged(const QString &name);
|
||||||
void generatedQuestionFinished(const QString &generatedQuestion);
|
void generatedQuestionFinished(const QString &generatedQuestion);
|
||||||
@ -188,17 +189,19 @@ Q_SIGNALS:
|
|||||||
void shouldBeLoadedChanged();
|
void shouldBeLoadedChanged();
|
||||||
void trySwitchContextRequested(const ModelInfo &modelInfo);
|
void trySwitchContextRequested(const ModelInfo &modelInfo);
|
||||||
void trySwitchContextOfLoadedModelCompleted(int value);
|
void trySwitchContextOfLoadedModelCompleted(int value);
|
||||||
void requestRetrieveFromDB(const QList<QString> &collections, const QString &text, int retrievalSize, QList<ResultInfo> *results);
|
void requestRetrieveFromDB(const QList<QString> &collections, const QString &text, int retrievalSize, QList<SourceExcerpt> *results);
|
||||||
void reportSpeed(const QString &speed);
|
void reportSpeed(const QString &speed);
|
||||||
void reportDevice(const QString &device);
|
void reportDevice(const QString &device);
|
||||||
void reportFallbackReason(const QString &fallbackReason);
|
void reportFallbackReason(const QString &fallbackReason);
|
||||||
void databaseResultsChanged(const QList<ResultInfo>&);
|
void sourceExcerptsChanged(const QList<SourceExcerpt>&);
|
||||||
void modelInfoChanged(const ModelInfo &modelInfo);
|
void modelInfoChanged(const ModelInfo &modelInfo);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool promptInternal(const QList<QString> &collectionList, const QString &prompt, const QString &promptTemplate,
|
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 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);
|
int32_t repeat_penalty_tokens);
|
||||||
|
bool toolCallInternal(const QString &toolcall, 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);
|
||||||
bool handlePrompt(int32_t token);
|
bool handlePrompt(int32_t token);
|
||||||
bool handleResponse(int32_t token, const std::string &response);
|
bool handleResponse(int32_t token, const std::string &response);
|
||||||
bool handleRecalculate(bool isRecalc);
|
bool handleRecalculate(bool isRecalc);
|
||||||
@ -244,11 +247,13 @@ private:
|
|||||||
bool m_reloadingToChangeVariant;
|
bool m_reloadingToChangeVariant;
|
||||||
bool m_processedSystemPrompt;
|
bool m_processedSystemPrompt;
|
||||||
bool m_restoreStateFromText;
|
bool m_restoreStateFromText;
|
||||||
|
bool m_maybeToolCall;
|
||||||
// m_pristineLoadedState is set if saveSate is unnecessary, either because:
|
// m_pristineLoadedState is set if saveSate is unnecessary, either because:
|
||||||
// - an unload was queued during LLModel::restoreState()
|
// - an unload was queued during LLModel::restoreState()
|
||||||
// - the chat will be restored from text and hasn't been interacted with yet
|
// - the chat will be restored from text and hasn't been interacted with yet
|
||||||
bool m_pristineLoadedState = false;
|
bool m_pristineLoadedState = false;
|
||||||
QVector<QPair<QString, QString>> m_stateFromText;
|
QVector<QPair<QString, QString>> m_stateFromText;
|
||||||
|
QNetworkAccessManager m_networkManager; // FIXME REMOVE
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CHATLLM_H
|
#endif // CHATLLM_H
|
||||||
|
@ -28,8 +28,8 @@ struct ChatItem
|
|||||||
Q_PROPERTY(bool stopped MEMBER stopped)
|
Q_PROPERTY(bool stopped MEMBER stopped)
|
||||||
Q_PROPERTY(bool thumbsUpState MEMBER thumbsUpState)
|
Q_PROPERTY(bool thumbsUpState MEMBER thumbsUpState)
|
||||||
Q_PROPERTY(bool thumbsDownState MEMBER thumbsDownState)
|
Q_PROPERTY(bool thumbsDownState MEMBER thumbsDownState)
|
||||||
Q_PROPERTY(QList<ResultInfo> sources MEMBER sources)
|
Q_PROPERTY(QList<SourceExcerpt> sources MEMBER sources)
|
||||||
Q_PROPERTY(QList<ResultInfo> consolidatedSources MEMBER consolidatedSources)
|
Q_PROPERTY(QList<SourceExcerpt> consolidatedSources MEMBER consolidatedSources)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// TODO: Maybe we should include the model name here as well as timestamp?
|
// TODO: Maybe we should include the model name here as well as timestamp?
|
||||||
@ -38,8 +38,8 @@ public:
|
|||||||
QString value;
|
QString value;
|
||||||
QString prompt;
|
QString prompt;
|
||||||
QString newResponse;
|
QString newResponse;
|
||||||
QList<ResultInfo> sources;
|
QList<SourceExcerpt> sources;
|
||||||
QList<ResultInfo> consolidatedSources;
|
QList<SourceExcerpt> consolidatedSources;
|
||||||
bool currentResponse = false;
|
bool currentResponse = false;
|
||||||
bool stopped = false;
|
bool stopped = false;
|
||||||
bool thumbsUpState = false;
|
bool thumbsUpState = false;
|
||||||
@ -200,20 +200,20 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<ResultInfo> consolidateSources(const QList<ResultInfo> &sources) {
|
QList<SourceExcerpt> consolidateSources(const QList<SourceExcerpt> &sources) {
|
||||||
QMap<QString, ResultInfo> groupedData;
|
QMap<QString, SourceExcerpt> groupedData;
|
||||||
for (const ResultInfo &info : sources) {
|
for (const SourceExcerpt &info : sources) {
|
||||||
if (groupedData.contains(info.file)) {
|
if (groupedData.contains(info.file)) {
|
||||||
groupedData[info.file].text += "\n---\n" + info.text;
|
groupedData[info.file].text += "\n---\n" + info.text;
|
||||||
} else {
|
} else {
|
||||||
groupedData[info.file] = info;
|
groupedData[info.file] = info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QList<ResultInfo> consolidatedSources = groupedData.values();
|
QList<SourceExcerpt> consolidatedSources = groupedData.values();
|
||||||
return consolidatedSources;
|
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;
|
if (index < 0 || index >= m_chatItems.size()) return;
|
||||||
|
|
||||||
@ -274,7 +274,7 @@ public:
|
|||||||
stream << c.thumbsDownState;
|
stream << c.thumbsDownState;
|
||||||
if (version > 7) {
|
if (version > 7) {
|
||||||
stream << c.sources.size();
|
stream << c.sources.size();
|
||||||
for (const ResultInfo &info : c.sources) {
|
for (const SourceExcerpt &info : c.sources) {
|
||||||
Q_ASSERT(!info.file.isEmpty());
|
Q_ASSERT(!info.file.isEmpty());
|
||||||
stream << info.collection;
|
stream << info.collection;
|
||||||
stream << info.path;
|
stream << info.path;
|
||||||
@ -286,12 +286,16 @@ public:
|
|||||||
stream << info.page;
|
stream << info.page;
|
||||||
stream << info.from;
|
stream << info.from;
|
||||||
stream << info.to;
|
stream << info.to;
|
||||||
|
if (version > 9) {
|
||||||
|
stream << info.url;
|
||||||
|
stream << info.favicon;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (version > 2) {
|
} else if (version > 2) {
|
||||||
QList<QString> references;
|
QList<QString> references;
|
||||||
QList<QString> referencesContext;
|
QList<QString> referencesContext;
|
||||||
int validReferenceNumber = 1;
|
int validReferenceNumber = 1;
|
||||||
for (const ResultInfo &info : c.sources) {
|
for (const SourceExcerpt &info : c.sources) {
|
||||||
if (info.file.isEmpty())
|
if (info.file.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -345,9 +349,9 @@ public:
|
|||||||
if (version > 7) {
|
if (version > 7) {
|
||||||
qsizetype count;
|
qsizetype count;
|
||||||
stream >> count;
|
stream >> count;
|
||||||
QList<ResultInfo> sources;
|
QList<SourceExcerpt> sources;
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
ResultInfo info;
|
SourceExcerpt info;
|
||||||
stream >> info.collection;
|
stream >> info.collection;
|
||||||
stream >> info.path;
|
stream >> info.path;
|
||||||
stream >> info.file;
|
stream >> info.file;
|
||||||
@ -358,6 +362,10 @@ public:
|
|||||||
stream >> info.page;
|
stream >> info.page;
|
||||||
stream >> info.from;
|
stream >> info.from;
|
||||||
stream >> info.to;
|
stream >> info.to;
|
||||||
|
if (version > 9) {
|
||||||
|
stream >> info.url;
|
||||||
|
stream >> info.favicon;
|
||||||
|
}
|
||||||
sources.append(info);
|
sources.append(info);
|
||||||
}
|
}
|
||||||
c.sources = sources;
|
c.sources = sources;
|
||||||
@ -369,7 +377,7 @@ public:
|
|||||||
stream >> referencesContext;
|
stream >> referencesContext;
|
||||||
|
|
||||||
if (!references.isEmpty()) {
|
if (!references.isEmpty()) {
|
||||||
QList<ResultInfo> sources;
|
QList<SourceExcerpt> sources;
|
||||||
QList<QString> referenceList = references.split("\n");
|
QList<QString> referenceList = references.split("\n");
|
||||||
|
|
||||||
// Ignore empty lines and those that begin with "---" which is no longer used
|
// Ignore empty lines and those that begin with "---" which is no longer used
|
||||||
@ -384,7 +392,7 @@ public:
|
|||||||
for (int j = 0; j < referenceList.size(); ++j) {
|
for (int j = 0; j < referenceList.size(); ++j) {
|
||||||
QString reference = referenceList[j];
|
QString reference = referenceList[j];
|
||||||
QString context = referencesContext[j];
|
QString context = referencesContext[j];
|
||||||
ResultInfo info;
|
SourceExcerpt info;
|
||||||
QTextStream refStream(&reference);
|
QTextStream refStream(&reference);
|
||||||
QString dummy;
|
QString dummy;
|
||||||
int validReferenceNumber;
|
int validReferenceNumber;
|
||||||
|
@ -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,
|
void Database::retrieveFromDB(const QList<QString> &collections, const QString &text, int retrievalSize,
|
||||||
QList<ResultInfo> *results)
|
QList<SourceExcerpt> *results)
|
||||||
{
|
{
|
||||||
#if defined(DEBUG)
|
#if defined(DEBUG)
|
||||||
qDebug() << "retrieveFromDB" << collections << text << retrievalSize;
|
qDebug() << "retrieveFromDB" << collections << text << retrievalSize;
|
||||||
@ -1974,7 +1974,7 @@ void Database::retrieveFromDB(const QList<QString> &collections, const QString &
|
|||||||
const int from = q.value(8).toInt();
|
const int from = q.value(8).toInt();
|
||||||
const int to = q.value(9).toInt();
|
const int to = q.value(9).toInt();
|
||||||
const QString collectionName = q.value(10).toString();
|
const QString collectionName = q.value(10).toString();
|
||||||
ResultInfo info;
|
SourceExcerpt info;
|
||||||
info.collection = collectionName;
|
info.collection = collectionName;
|
||||||
info.path = document_path;
|
info.path = document_path;
|
||||||
info.file = file;
|
info.file = file;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#define DATABASE_H
|
#define DATABASE_H
|
||||||
|
|
||||||
#include "embllm.h" // IWYU pragma: keep
|
#include "embllm.h" // IWYU pragma: keep
|
||||||
|
#include "sourceexcerpt.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QFileInfo>
|
#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 {
|
struct CollectionItem {
|
||||||
// -- Fields persisted to database --
|
// -- Fields persisted to database --
|
||||||
|
|
||||||
@ -158,7 +101,7 @@ public Q_SLOTS:
|
|||||||
void forceRebuildFolder(const QString &path);
|
void forceRebuildFolder(const QString &path);
|
||||||
bool addFolder(const QString &collection, const QString &path, const QString &embedding_model);
|
bool addFolder(const QString &collection, const QString &path, const QString &embedding_model);
|
||||||
void removeFolder(const QString &collection, const QString &path);
|
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, QList<SourceExcerpt> *results);
|
||||||
void changeChunkSize(int chunkSize);
|
void changeChunkSize(int chunkSize);
|
||||||
void changeFileExtensions(const QStringList &extensions);
|
void changeFileExtensions(const QStringList &extensions);
|
||||||
|
|
||||||
@ -225,7 +168,7 @@ private:
|
|||||||
QStringList m_scannedFileExtensions;
|
QStringList m_scannedFileExtensions;
|
||||||
QTimer *m_scanTimer;
|
QTimer *m_scanTimer;
|
||||||
QMap<int, QQueue<DocumentInfo>> m_docsToScan;
|
QMap<int, QQueue<DocumentInfo>> m_docsToScan;
|
||||||
QList<ResultInfo> m_retrieve;
|
QList<SourceExcerpt> m_retrieve;
|
||||||
QThread m_dbThread;
|
QThread m_dbThread;
|
||||||
QFileSystemWatcher *m_watcher;
|
QFileSystemWatcher *m_watcher;
|
||||||
QSet<QString> m_watchedPaths;
|
QSet<QString> m_watchedPaths;
|
||||||
|
@ -455,6 +455,7 @@ bool MySettings::localDocsUseRemoteEmbed() const { return getBasicSetting
|
|||||||
QString MySettings::localDocsNomicAPIKey() const { return getBasicSetting("localdocs/nomicAPIKey" ).toString(); }
|
QString MySettings::localDocsNomicAPIKey() const { return getBasicSetting("localdocs/nomicAPIKey" ).toString(); }
|
||||||
QString MySettings::localDocsEmbedDevice() const { return getBasicSetting("localdocs/embedDevice" ).toString(); }
|
QString MySettings::localDocsEmbedDevice() const { return getBasicSetting("localdocs/embedDevice" ).toString(); }
|
||||||
QString MySettings::networkAttribution() const { return getBasicSetting("network/attribution" ).toString(); }
|
QString MySettings::networkAttribution() const { return getBasicSetting("network/attribution" ).toString(); }
|
||||||
|
QString MySettings::braveSearchAPIKey() const { return getBasicSetting("bravesearch/APIKey" ).toString(); }
|
||||||
|
|
||||||
ChatTheme MySettings::chatTheme() const { return ChatTheme (getEnumSetting("chatTheme", chatThemeNames)); }
|
ChatTheme MySettings::chatTheme() const { return ChatTheme (getEnumSetting("chatTheme", chatThemeNames)); }
|
||||||
FontSize MySettings::fontSize() const { return FontSize (getEnumSetting("fontSize", fontSizeNames)); }
|
FontSize MySettings::fontSize() const { return FontSize (getEnumSetting("fontSize", fontSizeNames)); }
|
||||||
@ -473,6 +474,7 @@ void MySettings::setLocalDocsUseRemoteEmbed(bool value) { setBasic
|
|||||||
void MySettings::setLocalDocsNomicAPIKey(const QString &value) { setBasicSetting("localdocs/nomicAPIKey", value, "localDocsNomicAPIKey"); }
|
void MySettings::setLocalDocsNomicAPIKey(const QString &value) { setBasicSetting("localdocs/nomicAPIKey", value, "localDocsNomicAPIKey"); }
|
||||||
void MySettings::setLocalDocsEmbedDevice(const QString &value) { setBasicSetting("localdocs/embedDevice", value, "localDocsEmbedDevice"); }
|
void MySettings::setLocalDocsEmbedDevice(const QString &value) { setBasicSetting("localdocs/embedDevice", value, "localDocsEmbedDevice"); }
|
||||||
void MySettings::setNetworkAttribution(const QString &value) { setBasicSetting("network/attribution", value, "networkAttribution"); }
|
void MySettings::setNetworkAttribution(const QString &value) { setBasicSetting("network/attribution", value, "networkAttribution"); }
|
||||||
|
void MySettings::setBraveSearchAPIKey(const QString &value) { setBasicSetting("bravesearch/APIKey", value, "braveSearchAPIKey"); }
|
||||||
|
|
||||||
void MySettings::setChatTheme(ChatTheme value) { setBasicSetting("chatTheme", chatThemeNames .value(int(value))); }
|
void MySettings::setChatTheme(ChatTheme value) { setBasicSetting("chatTheme", chatThemeNames .value(int(value))); }
|
||||||
void MySettings::setFontSize(FontSize value) { setBasicSetting("fontSize", fontSizeNames .value(int(value))); }
|
void MySettings::setFontSize(FontSize value) { setBasicSetting("fontSize", fontSizeNames .value(int(value))); }
|
||||||
|
@ -71,6 +71,7 @@ class MySettings : public QObject
|
|||||||
Q_PROPERTY(int networkPort READ networkPort WRITE setNetworkPort NOTIFY networkPortChanged)
|
Q_PROPERTY(int networkPort READ networkPort WRITE setNetworkPort NOTIFY networkPortChanged)
|
||||||
Q_PROPERTY(SuggestionMode suggestionMode READ suggestionMode WRITE setSuggestionMode NOTIFY suggestionModeChanged)
|
Q_PROPERTY(SuggestionMode suggestionMode READ suggestionMode WRITE setSuggestionMode NOTIFY suggestionModeChanged)
|
||||||
Q_PROPERTY(QStringList uiLanguages MEMBER m_uiLanguages CONSTANT)
|
Q_PROPERTY(QStringList uiLanguages MEMBER m_uiLanguages CONSTANT)
|
||||||
|
Q_PROPERTY(QString braveSearchAPIKey READ braveSearchAPIKey WRITE setBraveSearchAPIKey NOTIFY braveSearchAPIKeyChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static MySettings *globalInstance();
|
static MySettings *globalInstance();
|
||||||
@ -184,6 +185,10 @@ public:
|
|||||||
QString localDocsEmbedDevice() const;
|
QString localDocsEmbedDevice() const;
|
||||||
void setLocalDocsEmbedDevice(const QString &value);
|
void setLocalDocsEmbedDevice(const QString &value);
|
||||||
|
|
||||||
|
// Tool settings
|
||||||
|
QString braveSearchAPIKey() const;
|
||||||
|
void setBraveSearchAPIKey(const QString &value);
|
||||||
|
|
||||||
// Network settings
|
// Network settings
|
||||||
QString networkAttribution() const;
|
QString networkAttribution() const;
|
||||||
void setNetworkAttribution(const QString &value);
|
void setNetworkAttribution(const QString &value);
|
||||||
@ -238,6 +243,7 @@ Q_SIGNALS:
|
|||||||
void deviceChanged();
|
void deviceChanged();
|
||||||
void suggestionModeChanged();
|
void suggestionModeChanged();
|
||||||
void languageAndLocaleChanged();
|
void languageAndLocaleChanged();
|
||||||
|
void braveSearchAPIKeyChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSettings m_settings;
|
QSettings m_settings;
|
||||||
|
@ -881,6 +881,8 @@ Rectangle {
|
|||||||
case Chat.PromptProcessing: return qsTr("processing ...")
|
case Chat.PromptProcessing: return qsTr("processing ...")
|
||||||
case Chat.ResponseGeneration: return qsTr("generating response ...");
|
case Chat.ResponseGeneration: return qsTr("generating response ...");
|
||||||
case Chat.GeneratingQuestions: return qsTr("generating questions ...");
|
case Chat.GeneratingQuestions: return qsTr("generating questions ...");
|
||||||
|
case Chat.ToolCalled: return currentChat.toolDescription;
|
||||||
|
case Chat.ToolProcessing: return qsTr("processing web results ..."); // FIXME should not be hardcoded!
|
||||||
default: return ""; // handle unexpected values
|
default: return ""; // handle unexpected values
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1131,7 +1133,7 @@ Rectangle {
|
|||||||
sourceSize.width: 24
|
sourceSize.width: 24
|
||||||
sourceSize.height: 24
|
sourceSize.height: 24
|
||||||
mipmap: true
|
mipmap: true
|
||||||
source: "qrc:/gpt4all/icons/db.svg"
|
source: consolidatedSources[0].url === "" ? "qrc:/gpt4all/icons/db.svg" : "qrc:/gpt4all/icons/globe.svg"
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorOverlay {
|
ColorOverlay {
|
||||||
@ -1243,11 +1245,15 @@ Rectangle {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: ma
|
id: ma
|
||||||
enabled: modelData.path !== ""
|
enabled: modelData.path !== "" || modelData.url !== ""
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
onClicked: function() {
|
onClicked: function() {
|
||||||
Qt.openUrlExternally(modelData.fileUri)
|
if (modelData.url !== "") {
|
||||||
|
console.log("opening url")
|
||||||
|
Qt.openUrlExternally(modelData.url)
|
||||||
|
} else
|
||||||
|
Qt.openUrlExternally(modelData.fileUri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1287,22 +1293,27 @@ Rectangle {
|
|||||||
Image {
|
Image {
|
||||||
id: fileIcon
|
id: fileIcon
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: false
|
visible: modelData.favicon !== ""
|
||||||
sourceSize.width: 24
|
sourceSize.width: 24
|
||||||
sourceSize.height: 24
|
sourceSize.height: 24
|
||||||
mipmap: true
|
mipmap: true
|
||||||
source: {
|
source: {
|
||||||
if (modelData.file.endsWith(".txt"))
|
if (modelData.favicon !== "")
|
||||||
|
return modelData.favicon;
|
||||||
|
else if (modelData.file.endsWith(".txt"))
|
||||||
return "qrc:/gpt4all/icons/file-txt.svg"
|
return "qrc:/gpt4all/icons/file-txt.svg"
|
||||||
else if (modelData.file.endsWith(".pdf"))
|
else if (modelData.file.endsWith(".pdf"))
|
||||||
return "qrc:/gpt4all/icons/file-pdf.svg"
|
return "qrc:/gpt4all/icons/file-pdf.svg"
|
||||||
else if (modelData.file.endsWith(".md"))
|
else if (modelData.file.endsWith(".md"))
|
||||||
return "qrc:/gpt4all/icons/file-md.svg"
|
return "qrc:/gpt4all/icons/file-md.svg"
|
||||||
else
|
else if (modelData.file !== "")
|
||||||
return "qrc:/gpt4all/icons/file.svg"
|
return "qrc:/gpt4all/icons/file.svg"
|
||||||
|
else
|
||||||
|
return "qrc:/gpt4all/icons/globe.svg"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ColorOverlay {
|
ColorOverlay {
|
||||||
|
visible: !fileIcon.visible
|
||||||
anchors.fill: fileIcon
|
anchors.fill: fileIcon
|
||||||
source: fileIcon
|
source: fileIcon
|
||||||
color: theme.textColor
|
color: theme.textColor
|
||||||
@ -1310,7 +1321,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
Layout.maximumWidth: 156
|
Layout.maximumWidth: 156
|
||||||
text: modelData.collection !== "" ? modelData.collection : qsTr("LocalDocs")
|
text: modelData.collection !== "" ? modelData.collection : modelData.title
|
||||||
font.pixelSize: theme.fontSizeLarge
|
font.pixelSize: theme.fontSizeLarge
|
||||||
font.bold: true
|
font.bold: true
|
||||||
color: theme.styledTextColor
|
color: theme.styledTextColor
|
||||||
@ -1326,7 +1337,7 @@ Rectangle {
|
|||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.maximumWidth: 180
|
Layout.maximumWidth: 180
|
||||||
Layout.maximumHeight: 55 - title.height
|
Layout.maximumHeight: 55 - title.height
|
||||||
text: modelData.file
|
text: modelData.file !== "" ? modelData.file : modelData.url
|
||||||
color: theme.textColor
|
color: theme.textColor
|
||||||
font.pixelSize: theme.fontSizeSmall
|
font.pixelSize: theme.fontSizeSmall
|
||||||
elide: Qt.ElideRight
|
elide: Qt.ElideRight
|
||||||
|
@ -34,6 +34,9 @@ Rectangle {
|
|||||||
ListElement {
|
ListElement {
|
||||||
title: qsTr("LocalDocs")
|
title: qsTr("LocalDocs")
|
||||||
}
|
}
|
||||||
|
ListElement {
|
||||||
|
title: qsTr("Tools")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
@ -152,6 +155,12 @@ Rectangle {
|
|||||||
Component { LocalDocsSettings { } }
|
Component { LocalDocsSettings { } }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MySettingsStack {
|
||||||
|
tabs: [
|
||||||
|
Component { ToolSettings { } }
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
71
gpt4all-chat/qml/ToolSettings.qml
Normal file
71
gpt4all-chat/qml/ToolSettings.qml
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
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.restoreLocalDocsDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
showRestoreDefaultsButton: true
|
||||||
|
|
||||||
|
title: qsTr("Tools")
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
id: root
|
||||||
|
spacing: 30
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 10
|
||||||
|
Label {
|
||||||
|
color: theme.grayRed900
|
||||||
|
font.pixelSize: theme.fontSizeLarge
|
||||||
|
font.bold: true
|
||||||
|
text: qsTr("Brave Search")
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 1
|
||||||
|
color: theme.grayRed500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
text: MySettings.braveSearchAPIKey
|
||||||
|
color: theme.textColor
|
||||||
|
font.pixelSize: theme.fontSizeLarge
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
Layout.minimumWidth: 200
|
||||||
|
onEditingFinished: {
|
||||||
|
MySettings.braveSearchAPIKey = apiKeyField.text;
|
||||||
|
}
|
||||||
|
Accessible.role: Accessible.EditableText
|
||||||
|
Accessible.name: apiKeyLabel.text
|
||||||
|
Accessible.description: apiKeyLabel.helpText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
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)
|
Server::Server(Chat *chat)
|
||||||
: ChatLLM(chat, true /*isServer*/)
|
: ChatLLM(chat, true /*isServer*/)
|
||||||
, m_chat(chat)
|
, m_chat(chat)
|
||||||
, m_server(nullptr)
|
, m_server(nullptr)
|
||||||
{
|
{
|
||||||
connect(this, &Server::threadStarted, this, &Server::start);
|
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);
|
connect(chat, &Chat::collectionListChanged, this, &Server::handleCollectionListChanged, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,7 +359,7 @@ QHttpServerResponse Server::handleCompletionRequest(const QHttpServerRequest &re
|
|||||||
|
|
||||||
int promptTokens = 0;
|
int promptTokens = 0;
|
||||||
int responseTokens = 0;
|
int responseTokens = 0;
|
||||||
QList<QPair<QString, QList<ResultInfo>>> responses;
|
QList<QPair<QString, QList<SourceExcerpt>>> responses;
|
||||||
for (int i = 0; i < n; ++i) {
|
for (int i = 0; i < n; ++i) {
|
||||||
if (!promptInternal(
|
if (!promptInternal(
|
||||||
m_collections,
|
m_collections,
|
||||||
@ -394,7 +380,7 @@ QHttpServerResponse Server::handleCompletionRequest(const QHttpServerRequest &re
|
|||||||
QString echoedPrompt = actualPrompt;
|
QString echoedPrompt = actualPrompt;
|
||||||
if (!echoedPrompt.endsWith("\n"))
|
if (!echoedPrompt.endsWith("\n"))
|
||||||
echoedPrompt += "\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)
|
if (!promptTokens)
|
||||||
promptTokens += m_promptTokens;
|
promptTokens += m_promptTokens;
|
||||||
responseTokens += m_promptResponseTokens - m_promptTokens;
|
responseTokens += m_promptResponseTokens - m_promptTokens;
|
||||||
@ -414,7 +400,7 @@ QHttpServerResponse Server::handleCompletionRequest(const QHttpServerRequest &re
|
|||||||
int index = 0;
|
int index = 0;
|
||||||
for (const auto &r : responses) {
|
for (const auto &r : responses) {
|
||||||
QString result = r.first;
|
QString result = r.first;
|
||||||
QList<ResultInfo> infos = r.second;
|
QList<SourceExcerpt> infos = r.second;
|
||||||
QJsonObject choice;
|
QJsonObject choice;
|
||||||
choice.insert("index", index++);
|
choice.insert("index", index++);
|
||||||
choice.insert("finish_reason", responseTokens == max_tokens ? "length" : "stop");
|
choice.insert("finish_reason", responseTokens == max_tokens ? "length" : "stop");
|
||||||
@ -425,7 +411,7 @@ QHttpServerResponse Server::handleCompletionRequest(const QHttpServerRequest &re
|
|||||||
if (MySettings::globalInstance()->localDocsShowReferences()) {
|
if (MySettings::globalInstance()->localDocsShowReferences()) {
|
||||||
QJsonArray references;
|
QJsonArray references;
|
||||||
for (const auto &ref : infos)
|
for (const auto &ref : infos)
|
||||||
references.append(resultToJson(ref));
|
references.append(ref.toJson());
|
||||||
choice.insert("references", references);
|
choice.insert("references", references);
|
||||||
}
|
}
|
||||||
choices.append(choice);
|
choices.append(choice);
|
||||||
@ -434,7 +420,7 @@ QHttpServerResponse Server::handleCompletionRequest(const QHttpServerRequest &re
|
|||||||
int index = 0;
|
int index = 0;
|
||||||
for (const auto &r : responses) {
|
for (const auto &r : responses) {
|
||||||
QString result = r.first;
|
QString result = r.first;
|
||||||
QList<ResultInfo> infos = r.second;
|
QList<SourceExcerpt> infos = r.second;
|
||||||
QJsonObject choice;
|
QJsonObject choice;
|
||||||
choice.insert("text", result);
|
choice.insert("text", result);
|
||||||
choice.insert("index", index++);
|
choice.insert("index", index++);
|
||||||
@ -443,7 +429,7 @@ QHttpServerResponse Server::handleCompletionRequest(const QHttpServerRequest &re
|
|||||||
if (MySettings::globalInstance()->localDocsShowReferences()) {
|
if (MySettings::globalInstance()->localDocsShowReferences()) {
|
||||||
QJsonArray references;
|
QJsonArray references;
|
||||||
for (const auto &ref : infos)
|
for (const auto &ref : infos)
|
||||||
references.append(resultToJson(ref));
|
references.append(ref.toJson());
|
||||||
choice.insert("references", references);
|
choice.insert("references", references);
|
||||||
}
|
}
|
||||||
choices.append(choice);
|
choices.append(choice);
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
#define SERVER_H
|
#define SERVER_H
|
||||||
|
|
||||||
#include "chatllm.h"
|
#include "chatllm.h"
|
||||||
#include "database.h"
|
#include "sourceexcerpt.h"
|
||||||
|
|
||||||
#include <QHttpServerRequest>
|
#include <QHttpServerRequest>
|
||||||
#include <QHttpServerResponse>
|
#include <QHttpServerResponse>
|
||||||
@ -29,13 +29,13 @@ Q_SIGNALS:
|
|||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
QHttpServerResponse handleCompletionRequest(const QHttpServerRequest &request, bool isChat);
|
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; }
|
void handleCollectionListChanged(const QList<QString> &collectionList) { m_collections = collectionList; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Chat *m_chat;
|
Chat *m_chat;
|
||||||
QHttpServer *m_server;
|
QHttpServer *m_server;
|
||||||
QList<ResultInfo> m_databaseResults;
|
QList<SourceExcerpt> m_sourceExcerpts;
|
||||||
QList<QString> m_collections;
|
QList<QString> m_collections;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
95
gpt4all-chat/sourceexcerpt.h
Normal file
95
gpt4all-chat/sourceexcerpt.h
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
#ifndef SOURCEEXCERT_H
|
||||||
|
#define SOURCEEXCERT_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
|
struct SourceExcerpt {
|
||||||
|
Q_GADGET
|
||||||
|
Q_PROPERTY(QString date MEMBER date)
|
||||||
|
Q_PROPERTY(QString text MEMBER text)
|
||||||
|
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(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 date; // [Required] The creation or the last modification date whichever is latest
|
||||||
|
QString text; // [Required] The text actually used in the augmented context
|
||||||
|
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
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject toJson() const
|
||||||
|
{
|
||||||
|
QJsonObject result;
|
||||||
|
result.insert("date", date);
|
||||||
|
result.insert("text", text);
|
||||||
|
result.insert("collection", collection);
|
||||||
|
result.insert("path", path);
|
||||||
|
result.insert("file", file);
|
||||||
|
result.insert("url", url);
|
||||||
|
result.insert("favicon", favicon);
|
||||||
|
result.insert("title", title);
|
||||||
|
result.insert("author", author);
|
||||||
|
result.insert("page", page);
|
||||||
|
result.insert("from", from);
|
||||||
|
result.insert("to", to);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const SourceExcerpt &other) const {
|
||||||
|
return date == other.date &&
|
||||||
|
text == other.text &&
|
||||||
|
collection == other.collection &&
|
||||||
|
path == other.path &&
|
||||||
|
file == other.file &&
|
||||||
|
url == other.url &&
|
||||||
|
favicon == other.favicon &&
|
||||||
|
title == other.title &&
|
||||||
|
author == other.author &&
|
||||||
|
page == other.page &&
|
||||||
|
from == other.from &&
|
||||||
|
to == other.to;
|
||||||
|
}
|
||||||
|
bool operator!=(const SourceExcerpt &other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(SourceExcerpt)
|
||||||
|
|
||||||
|
#endif // SOURCEEXCERT_H
|
Loading…
x
Reference in New Issue
Block a user