chat: generate follow-up questions after response (#2634)

* user can configure the prompt and when they appear
* also make the name generation prompt configurable

Signed-off-by: Adam Treat <treat.adam@gmail.com>
Signed-off-by: Jared Van Bortel <jared@nomic.ai>
Co-authored-by: Jared Van Bortel <jared@nomic.ai>
This commit is contained in:
AT 2024-07-10 15:45:20 -04:00 committed by GitHub
parent ef4e362d92
commit 66bc04aa8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 621 additions and 138 deletions

View File

@ -210,11 +210,13 @@ qt_add_qml_module(chat
icons/network.svg icons/network.svg
icons/nomic_logo.svg icons/nomic_logo.svg
icons/notes.svg icons/notes.svg
icons/plus.svg
icons/recycle.svg icons/recycle.svg
icons/regenerate.svg icons/regenerate.svg
icons/search.svg icons/search.svg
icons/send_message.svg icons/send_message.svg
icons/settings.svg icons/settings.svg
icons/stack.svg
icons/stop_generating.svg icons/stop_generating.svg
icons/thumbs_down.svg icons/thumbs_down.svg
icons/thumbs_up.svg icons/thumbs_up.svg

View File

@ -58,11 +58,13 @@ void Chat::connectLLM()
connect(m_llmodel, &ChatLLM::modelLoadingPercentageChanged, this, &Chat::handleModelLoadingPercentageChanged, Qt::QueuedConnection); connect(m_llmodel, &ChatLLM::modelLoadingPercentageChanged, this, &Chat::handleModelLoadingPercentageChanged, Qt::QueuedConnection);
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::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);
connect(m_llmodel, &ChatLLM::recalcChanged, this, &Chat::handleRecalculating, Qt::QueuedConnection); connect(m_llmodel, &ChatLLM::recalcChanged, this, &Chat::handleRecalculating, Qt::QueuedConnection);
connect(m_llmodel, &ChatLLM::generatedNameChanged, this, &Chat::generatedNameChanged, Qt::QueuedConnection); connect(m_llmodel, &ChatLLM::generatedNameChanged, this, &Chat::generatedNameChanged, Qt::QueuedConnection);
connect(m_llmodel, &ChatLLM::generatedQuestionFinished, this, &Chat::generatedQuestionFinished, Qt::QueuedConnection);
connect(m_llmodel, &ChatLLM::reportSpeed, this, &Chat::handleTokenSpeedChanged, Qt::QueuedConnection); connect(m_llmodel, &ChatLLM::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::databaseResultsChanged, this, &Chat::handleDatabaseResultsChanged, Qt::QueuedConnection);
@ -113,6 +115,8 @@ void Chat::resetResponseState()
if (m_responseInProgress && m_responseState == Chat::LocalDocsRetrieval) if (m_responseInProgress && m_responseState == Chat::LocalDocsRetrieval)
return; return;
m_generatedQuestions = QList<QString>();
emit generatedQuestionsChanged();
m_tokenSpeed = QString(); m_tokenSpeed = QString();
emit tokenSpeedChanged(); emit tokenSpeedChanged();
m_responseInProgress = true; m_responseInProgress = true;
@ -189,6 +193,12 @@ void Chat::promptProcessing()
emit responseStateChanged(); emit responseStateChanged();
} }
void Chat::generatingQuestions()
{
m_responseState = Chat::GeneratingQuestions;
emit responseStateChanged();
}
void Chat::responseStopped(qint64 promptResponseMs) void Chat::responseStopped(qint64 promptResponseMs)
{ {
m_tokenSpeed = QString(); m_tokenSpeed = QString();
@ -304,6 +314,12 @@ void Chat::generatedNameChanged(const QString &name)
emit nameChanged(); emit nameChanged();
} }
void Chat::generatedQuestionFinished(const QString &question)
{
m_generatedQuestions << question;
emit generatedQuestionsChanged();
}
void Chat::handleRecalculating() void Chat::handleRecalculating()
{ {
Network::globalInstance()->trackChatEvent("recalc_context", { {"length", m_chatModel->count()} }); Network::globalInstance()->trackChatEvent("recalc_context", { {"length", m_chatModel->count()} });

View File

@ -39,6 +39,7 @@ class Chat : public QObject
Q_PROPERTY(LocalDocsCollectionsModel *collectionModel READ collectionModel NOTIFY collectionModelChanged) Q_PROPERTY(LocalDocsCollectionsModel *collectionModel READ collectionModel NOTIFY collectionModelChanged)
// 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)
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("Only creatable from c++!") QML_UNCREATABLE("Only creatable from c++!")
@ -48,6 +49,7 @@ public:
LocalDocsRetrieval, LocalDocsRetrieval,
LocalDocsProcessing, LocalDocsProcessing,
PromptProcessing, PromptProcessing,
GeneratingQuestions,
ResponseGeneration ResponseGeneration
}; };
Q_ENUM(ResponseState) Q_ENUM(ResponseState)
@ -119,6 +121,8 @@ public:
int trySwitchContextInProgress() const { return m_trySwitchContextInProgress; } int trySwitchContextInProgress() const { return m_trySwitchContextInProgress; }
QList<QString> generatedQuestions() const { return m_generatedQuestions; }
public Q_SLOTS: public Q_SLOTS:
void serverNewPromptResponsePair(const QString &prompt); void serverNewPromptResponsePair(const QString &prompt);
@ -153,13 +157,16 @@ Q_SIGNALS:
void collectionModelChanged(); void collectionModelChanged();
void trySwitchContextInProgressChanged(); void trySwitchContextInProgressChanged();
void loadedModelInfoChanged(); void loadedModelInfoChanged();
void generatedQuestionsChanged();
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 responseStopped(qint64 promptResponseMs); void responseStopped(qint64 promptResponseMs);
void generatedNameChanged(const QString &name); void generatedNameChanged(const QString &name);
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);
@ -179,6 +186,7 @@ private:
QString m_fallbackReason; QString m_fallbackReason;
QString m_response; QString m_response;
QList<QString> m_collections; QList<QString> m_collections;
QList<QString> m_generatedQuestions;
ChatModel *m_chatModel; ChatModel *m_chatModel;
bool m_responseInProgress = false; bool m_responseInProgress = false;
ResponseState m_responseState; ResponseState m_responseState;

View File

@ -750,7 +750,7 @@ bool ChatLLM::promptInternal(const QList<QString> &collectionList, const QString
if (!databaseResults.isEmpty()) { if (!databaseResults.isEmpty()) {
QStringList results; QStringList results;
for (const ResultInfo &info : databaseResults) for (const ResultInfo &info : databaseResults)
results << u"Collection: %1\nPath: %2\nSnippet: %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
docsContext = u"### Context:\n%1\n\n"_s.arg(results.join("\n\n")); docsContext = u"### Context:\n%1\n\n"_s.arg(results.join("\n\n"));
@ -797,7 +797,13 @@ bool ChatLLM::promptInternal(const QList<QString> &collectionList, const QString
m_response = trimmed; m_response = trimmed;
emit responseChanged(QString::fromStdString(m_response)); emit responseChanged(QString::fromStdString(m_response));
} }
SuggestionMode mode = MySettings::globalInstance()->suggestionMode();
if (mode == SuggestionMode::On || (!databaseResults.isEmpty() && mode == SuggestionMode::LocalDocsOnly))
generateQuestions(elapsed);
else
emit responseStopped(elapsed); emit responseStopped(elapsed);
m_pristineLoadedState = false; m_pristineLoadedState = false;
return true; return true;
} }
@ -875,13 +881,19 @@ void ChatLLM::generateName()
if (!isModelLoaded()) if (!isModelLoaded())
return; return;
const QString chatNamePrompt = MySettings::globalInstance()->modelChatNamePrompt(m_modelInfo);
if (chatNamePrompt.trimmed().isEmpty()) {
qWarning() << "ChatLLM: not generating chat name because prompt is empty";
return;
}
auto promptTemplate = MySettings::globalInstance()->modelPromptTemplate(m_modelInfo); auto promptTemplate = MySettings::globalInstance()->modelPromptTemplate(m_modelInfo);
auto promptFunc = std::bind(&ChatLLM::handleNamePrompt, this, std::placeholders::_1); auto promptFunc = std::bind(&ChatLLM::handleNamePrompt, this, std::placeholders::_1);
auto responseFunc = std::bind(&ChatLLM::handleNameResponse, this, std::placeholders::_1, std::placeholders::_2); auto responseFunc = std::bind(&ChatLLM::handleNameResponse, this, std::placeholders::_1, std::placeholders::_2);
auto recalcFunc = std::bind(&ChatLLM::handleNameRecalculate, this, std::placeholders::_1); auto recalcFunc = std::bind(&ChatLLM::handleNameRecalculate, this, std::placeholders::_1);
LLModel::PromptContext ctx = m_ctx; LLModel::PromptContext ctx = m_ctx;
m_llModelInfo.model->prompt("Describe the above conversation in seven words or less.", m_llModelInfo.model->prompt(chatNamePrompt.toStdString(), promptTemplate.toStdString(),
promptTemplate.toStdString(), promptFunc, responseFunc, recalcFunc, ctx); promptFunc, responseFunc, recalcFunc, ctx);
std::string trimmed = trim_whitespace(m_nameResponse); std::string trimmed = trim_whitespace(m_nameResponse);
if (trimmed != m_nameResponse) { if (trimmed != m_nameResponse) {
m_nameResponse = trimmed; m_nameResponse = trimmed;
@ -901,7 +913,6 @@ bool ChatLLM::handleNamePrompt(int32_t token)
qDebug() << "name prompt" << m_llmThread.objectName() << token; qDebug() << "name prompt" << m_llmThread.objectName() << token;
#endif #endif
Q_UNUSED(token); Q_UNUSED(token);
qt_noop();
return !m_stopGenerating; return !m_stopGenerating;
} }
@ -925,10 +936,84 @@ bool ChatLLM::handleNameRecalculate(bool isRecalc)
qDebug() << "name recalc" << m_llmThread.objectName() << isRecalc; qDebug() << "name recalc" << m_llmThread.objectName() << isRecalc;
#endif #endif
Q_UNUSED(isRecalc); Q_UNUSED(isRecalc);
qt_noop();
return true; return true;
} }
bool ChatLLM::handleQuestionPrompt(int32_t token)
{
#if defined(DEBUG)
qDebug() << "question prompt" << m_llmThread.objectName() << token;
#endif
Q_UNUSED(token);
return !m_stopGenerating;
}
bool ChatLLM::handleQuestionResponse(int32_t token, const std::string &response)
{
#if defined(DEBUG)
qDebug() << "question response" << m_llmThread.objectName() << token << response;
#endif
Q_UNUSED(token);
// add token to buffer
m_questionResponse.append(response);
// match whole question sentences
static const QRegularExpression reQuestion(R"(\b(What|Where|How|Why|When|Who|Which|Whose|Whom)\b[^?]*\?)");
// extract all questions from response
int lastMatchEnd = -1;
for (const auto &match : reQuestion.globalMatch(m_questionResponse)) {
lastMatchEnd = match.capturedEnd();
emit generatedQuestionFinished(match.captured());
}
// remove processed input from buffer
if (lastMatchEnd != -1)
m_questionResponse.erase(m_questionResponse.cbegin(), m_questionResponse.cbegin() + lastMatchEnd);
return true;
}
bool ChatLLM::handleQuestionRecalculate(bool isRecalc)
{
#if defined(DEBUG)
qDebug() << "name recalc" << m_llmThread.objectName() << isRecalc;
#endif
Q_UNUSED(isRecalc);
return true;
}
void ChatLLM::generateQuestions(qint64 elapsed)
{
Q_ASSERT(isModelLoaded());
if (!isModelLoaded()) {
emit responseStopped(elapsed);
return;
}
const std::string suggestedFollowUpPrompt = MySettings::globalInstance()->modelSuggestedFollowUpPrompt(m_modelInfo).toStdString();
if (QString::fromStdString(suggestedFollowUpPrompt).trimmed().isEmpty()) {
emit responseStopped(elapsed);
return;
}
emit generatingQuestions();
m_questionResponse.clear();
auto promptTemplate = MySettings::globalInstance()->modelPromptTemplate(m_modelInfo);
auto promptFunc = std::bind(&ChatLLM::handleQuestionPrompt, this, std::placeholders::_1);
auto responseFunc = std::bind(&ChatLLM::handleQuestionResponse, this, std::placeholders::_1, std::placeholders::_2);
auto recalcFunc = std::bind(&ChatLLM::handleQuestionRecalculate, this, std::placeholders::_1);
LLModel::PromptContext ctx = m_ctx;
QElapsedTimer totalTime;
totalTime.start();
m_llModelInfo.model->prompt(suggestedFollowUpPrompt,
promptTemplate.toStdString(), promptFunc, responseFunc, recalcFunc, ctx);
elapsed += totalTime.elapsed();
emit responseStopped(elapsed);
}
bool ChatLLM::handleSystemPrompt(int32_t token) bool ChatLLM::handleSystemPrompt(int32_t token)
{ {
#if defined(DEBUG) #if defined(DEBUG)

View File

@ -160,6 +160,7 @@ public Q_SLOTS:
void unloadModel(); void unloadModel();
void reloadModel(); void reloadModel();
void generateName(); void generateName();
void generateQuestions(qint64 elapsed);
void handleChatIdChanged(const QString &id); void handleChatIdChanged(const QString &id);
void handleShouldBeLoadedChanged(); void handleShouldBeLoadedChanged();
void handleThreadStarted(); void handleThreadStarted();
@ -176,8 +177,10 @@ Q_SIGNALS:
void modelLoadingWarning(const QString &warning); void modelLoadingWarning(const QString &warning);
void responseChanged(const QString &response); void responseChanged(const QString &response);
void promptProcessing(); void promptProcessing();
void generatingQuestions();
void responseStopped(qint64 promptResponseMs); void responseStopped(qint64 promptResponseMs);
void generatedNameChanged(const QString &name); void generatedNameChanged(const QString &name);
void generatedQuestionFinished(const QString &generatedQuestion);
void stateChanged(); void stateChanged();
void threadStarted(); void threadStarted();
void shouldBeLoadedChanged(); void shouldBeLoadedChanged();
@ -206,6 +209,9 @@ protected:
bool handleRestoreStateFromTextPrompt(int32_t token); bool handleRestoreStateFromTextPrompt(int32_t token);
bool handleRestoreStateFromTextResponse(int32_t token, const std::string &response); bool handleRestoreStateFromTextResponse(int32_t token, const std::string &response);
bool handleRestoreStateFromTextRecalculate(bool isRecalc); bool handleRestoreStateFromTextRecalculate(bool isRecalc);
bool handleQuestionPrompt(int32_t token);
bool handleQuestionResponse(int32_t token, const std::string &response);
bool handleQuestionRecalculate(bool isRecalc);
void saveState(); void saveState();
void restoreState(); void restoreState();
@ -219,6 +225,7 @@ private:
std::string m_response; std::string m_response;
std::string m_nameResponse; std::string m_nameResponse;
QString m_questionResponse;
LLModelInfo m_llModelInfo; LLModelInfo m_llModelInfo;
LLModelType m_llModelType; LLModelType m_llModelType;
ModelInfo m_modelInfo; ModelInfo m_modelInfo;

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z"></path></svg>

After

Width:  |  Height:  |  Size: 236 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M230.91,124A8,8,0,0,1,228,134.91l-96,56a8,8,0,0,1-8.06,0l-96-56A8,8,0,0,1,36,121.09l92,53.65,92-53.65A8,8,0,0,1,230.91,124ZM24,80a8,8,0,0,1,4-6.91l96-56a8,8,0,0,1,8.06,0l96,56a8,8,0,0,1,0,13.82l-96,56a8,8,0,0,1-8.06,0l-96-56A8,8,0,0,1,24,80Zm23.88,0L128,126.74,208.12,80,128,33.26ZM232,192H216V176a8,8,0,0,0-16,0v16H184a8,8,0,0,0,0,16h16v16a8,8,0,0,0,16,0V208h16a8,8,0,0,0,0-16Zm-92,23.76-12,7L36,169.09A8,8,0,0,0,28,182.91l96,56a8,8,0,0,0,8.06,0l16-9.33A8,8,0,1,0,140,215.76Z"></path></svg>

After

Width:  |  Height:  |  Size: 600 B

View File

@ -334,6 +334,28 @@ void ModelInfo::setSystemPrompt(const QString &p)
m_systemPrompt = p; m_systemPrompt = p;
} }
QString ModelInfo::chatNamePrompt() const
{
return MySettings::globalInstance()->modelChatNamePrompt(*this);
}
void ModelInfo::setChatNamePrompt(const QString &p)
{
if (shouldSaveMetadata()) MySettings::globalInstance()->setModelChatNamePrompt(*this, p, true /*force*/);
m_chatNamePrompt = p;
}
QString ModelInfo::suggestedFollowUpPrompt() const
{
return MySettings::globalInstance()->modelSuggestedFollowUpPrompt(*this);
}
void ModelInfo::setSuggestedFollowUpPrompt(const QString &p)
{
if (shouldSaveMetadata()) MySettings::globalInstance()->setModelSuggestedFollowUpPrompt(*this, p, true /*force*/);
m_suggestedFollowUpPrompt = p;
}
bool ModelInfo::shouldSaveMetadata() const bool ModelInfo::shouldSaveMetadata() const
{ {
return installed && (isClone() || isDiscovered() || description() == "" /*indicates sideloaded*/); return installed && (isClone() || isDiscovered() || description() == "" /*indicates sideloaded*/);
@ -364,6 +386,8 @@ QVariantMap ModelInfo::getFields() const
{ "repeatPenaltyTokens", m_repeatPenaltyTokens }, { "repeatPenaltyTokens", m_repeatPenaltyTokens },
{ "promptTemplate", m_promptTemplate }, { "promptTemplate", m_promptTemplate },
{ "systemPrompt", m_systemPrompt }, { "systemPrompt", m_systemPrompt },
{ "chatNamePrompt", m_chatNamePrompt },
{ "suggestedFollowUpPrompt", m_suggestedFollowUpPrompt },
}; };
} }
@ -758,6 +782,10 @@ QVariant ModelList::dataInternal(const ModelInfo *info, int role) const
return info->promptTemplate(); return info->promptTemplate();
case SystemPromptRole: case SystemPromptRole:
return info->systemPrompt(); return info->systemPrompt();
case ChatNamePromptRole:
return info->chatNamePrompt();
case SuggestedFollowUpPromptRole:
return info->suggestedFollowUpPrompt();
case LikesRole: case LikesRole:
return info->likes(); return info->likes();
case DownloadsRole: case DownloadsRole:
@ -928,6 +956,10 @@ void ModelList::updateData(const QString &id, const QVector<QPair<int, QVariant>
info->setPromptTemplate(value.toString()); break; info->setPromptTemplate(value.toString()); break;
case SystemPromptRole: case SystemPromptRole:
info->setSystemPrompt(value.toString()); break; info->setSystemPrompt(value.toString()); break;
case ChatNamePromptRole:
info->setChatNamePrompt(value.toString()); break;
case SuggestedFollowUpPromptRole:
info->setSuggestedFollowUpPrompt(value.toString()); break;
case LikesRole: case LikesRole:
{ {
if (info->likes() != value.toInt()) { if (info->likes() != value.toInt()) {
@ -1077,6 +1109,8 @@ QString ModelList::clone(const ModelInfo &model)
{ ModelList::RepeatPenaltyTokensRole, model.repeatPenaltyTokens() }, { ModelList::RepeatPenaltyTokensRole, model.repeatPenaltyTokens() },
{ ModelList::PromptTemplateRole, model.promptTemplate() }, { ModelList::PromptTemplateRole, model.promptTemplate() },
{ ModelList::SystemPromptRole, model.systemPrompt() }, { ModelList::SystemPromptRole, model.systemPrompt() },
{ ModelList::ChatNamePromptRole, model.chatNamePrompt() },
{ ModelList::SuggestedFollowUpPromptRole, model.suggestedFollowUpPrompt() },
}; };
updateData(id, data); updateData(id, data);
return id; return id;
@ -1772,6 +1806,14 @@ void ModelList::updateModelsFromSettings()
const QString systemPrompt = settings.value(g + "/systemPrompt").toString(); const QString systemPrompt = settings.value(g + "/systemPrompt").toString();
data.append({ ModelList::SystemPromptRole, systemPrompt }); data.append({ ModelList::SystemPromptRole, systemPrompt });
} }
if (settings.contains(g + "/chatNamePrompt")) {
const QString chatNamePrompt = settings.value(g + "/chatNamePrompt").toString();
data.append({ ModelList::ChatNamePromptRole, chatNamePrompt });
}
if (settings.contains(g + "/suggestedFollowUpPrompt")) {
const QString suggestedFollowUpPrompt = settings.value(g + "/suggestedFollowUpPrompt").toString();
data.append({ ModelList::SuggestedFollowUpPromptRole, suggestedFollowUpPrompt });
}
updateData(id, data); updateData(id, data);
} }
} }

View File

@ -68,6 +68,8 @@ struct ModelInfo {
Q_PROPERTY(int repeatPenaltyTokens READ repeatPenaltyTokens WRITE setRepeatPenaltyTokens) Q_PROPERTY(int repeatPenaltyTokens READ repeatPenaltyTokens WRITE setRepeatPenaltyTokens)
Q_PROPERTY(QString promptTemplate READ promptTemplate WRITE setPromptTemplate) Q_PROPERTY(QString promptTemplate READ promptTemplate WRITE setPromptTemplate)
Q_PROPERTY(QString systemPrompt READ systemPrompt WRITE setSystemPrompt) Q_PROPERTY(QString systemPrompt READ systemPrompt WRITE setSystemPrompt)
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 likes READ likes WRITE setLikes)
Q_PROPERTY(int downloads READ downloads WRITE setDownloads) Q_PROPERTY(int downloads READ downloads WRITE setDownloads)
Q_PROPERTY(QDateTime recency READ recency WRITE setRecency) Q_PROPERTY(QDateTime recency READ recency WRITE setRecency)
@ -167,6 +169,10 @@ public:
void setPromptTemplate(const QString &t); void setPromptTemplate(const QString &t);
QString systemPrompt() const; QString systemPrompt() const;
void setSystemPrompt(const QString &p); void setSystemPrompt(const QString &p);
QString chatNamePrompt() const;
void setChatNamePrompt(const QString &p);
QString suggestedFollowUpPrompt() const;
void setSuggestedFollowUpPrompt(const QString &p);
bool shouldSaveMetadata() const; bool shouldSaveMetadata() const;
@ -199,6 +205,8 @@ private:
int m_repeatPenaltyTokens = 64; int m_repeatPenaltyTokens = 64;
QString m_promptTemplate = "### Human:\n%1\n\n### Assistant:\n"; 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_systemPrompt = "### 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.";
friend class MySettings; friend class MySettings;
}; };
Q_DECLARE_METATYPE(ModelInfo) Q_DECLARE_METATYPE(ModelInfo)
@ -317,6 +325,8 @@ public:
RepeatPenaltyTokensRole, RepeatPenaltyTokensRole,
PromptTemplateRole, PromptTemplateRole,
SystemPromptRole, SystemPromptRole,
ChatNamePromptRole,
SuggestedFollowUpPromptRole,
MinPRole, MinPRole,
LikesRole, LikesRole,
DownloadsRole, DownloadsRole,
@ -368,6 +378,8 @@ public:
roles[RepeatPenaltyTokensRole] = "repeatPenaltyTokens"; roles[RepeatPenaltyTokensRole] = "repeatPenaltyTokens";
roles[PromptTemplateRole] = "promptTemplate"; roles[PromptTemplateRole] = "promptTemplate";
roles[SystemPromptRole] = "systemPrompt"; roles[SystemPromptRole] = "systemPrompt";
roles[ChatNamePromptRole] = "chatNamePrompt";
roles[SuggestedFollowUpPromptRole] = "suggestedFollowUpPrompt";
roles[LikesRole] = "likes"; roles[LikesRole] = "likes";
roles[DownloadsRole] = "downloads"; roles[DownloadsRole] = "downloads";
roles[RecencyRole] = "recency"; roles[RecencyRole] = "recency";

View File

@ -41,6 +41,7 @@ static const QVariantMap basicDefaults {
{ "saveChatsContext", false }, { "saveChatsContext", false },
{ "serverChat", false }, { "serverChat", false },
{ "userDefaultModel", "Application default" }, { "userDefaultModel", "Application default" },
{ "suggestionMode", QVariant::fromValue(SuggestionMode::LocalDocsOnly) },
{ "localdocs/chunkSize", 512 }, { "localdocs/chunkSize", 512 },
{ "localdocs/retrievalSize", 3 }, { "localdocs/retrievalSize", 3 },
{ "localdocs/showReferences", true }, { "localdocs/showReferences", true },
@ -136,6 +137,8 @@ void MySettings::restoreModelDefaults(const ModelInfo &info)
setModelRepeatPenaltyTokens(info, info.m_repeatPenaltyTokens); setModelRepeatPenaltyTokens(info, info.m_repeatPenaltyTokens);
setModelPromptTemplate(info, info.m_promptTemplate); setModelPromptTemplate(info, info.m_promptTemplate);
setModelSystemPrompt(info, info.m_systemPrompt); setModelSystemPrompt(info, info.m_systemPrompt);
setModelChatNamePrompt(info, info.m_chatNamePrompt);
setModelSuggestedFollowUpPrompt(info, info.m_suggestedFollowUpPrompt);
} }
void MySettings::restoreApplicationDefaults() void MySettings::restoreApplicationDefaults()
@ -150,6 +153,7 @@ void MySettings::restoreApplicationDefaults()
setModelPath(defaultLocalModelsPath()); setModelPath(defaultLocalModelsPath());
setUserDefaultModel(basicDefaults.value("userDefaultModel").toString()); setUserDefaultModel(basicDefaults.value("userDefaultModel").toString());
setForceMetal(defaults::forceMetal); setForceMetal(defaults::forceMetal);
setSuggestionMode(basicDefaults.value("suggestionMode").value<SuggestionMode>());
} }
void MySettings::restoreLocalDocsDefaults() void MySettings::restoreLocalDocsDefaults()
@ -231,9 +235,11 @@ int MySettings::modelPromptBatchSize (const ModelInfo &info) const { re
int MySettings::modelContextLength (const ModelInfo &info) const { return getModelSetting("contextLength", info).toInt(); } int MySettings::modelContextLength (const ModelInfo &info) const { return getModelSetting("contextLength", info).toInt(); }
int MySettings::modelGpuLayers (const ModelInfo &info) const { return getModelSetting("gpuLayers", info).toInt(); } int MySettings::modelGpuLayers (const ModelInfo &info) const { return getModelSetting("gpuLayers", info).toInt(); }
double MySettings::modelRepeatPenalty (const ModelInfo &info) const { return getModelSetting("repeatPenalty", info).toDouble(); } double MySettings::modelRepeatPenalty (const ModelInfo &info) const { return getModelSetting("repeatPenalty", info).toDouble(); }
int MySettings::modelRepeatPenaltyTokens(const ModelInfo &info) const { return getModelSetting("repeatPenaltyTokens", info).toInt(); } 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::modelPromptTemplate (const ModelInfo &info) const { return getModelSetting("promptTemplate", info).toString(); }
QString MySettings::modelSystemPrompt (const ModelInfo &info) const { return getModelSetting("systemPrompt", info).toString(); } QString MySettings::modelSystemPrompt (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(); }
void MySettings::setModelFilename(const ModelInfo &info, const QString &value, bool force) void MySettings::setModelFilename(const ModelInfo &info, const QString &value, bool force)
{ {
@ -345,6 +351,16 @@ void MySettings::setModelSystemPrompt(const ModelInfo &info, const QString &valu
setModelSetting("systemPrompt", info, value, force, true); setModelSetting("systemPrompt", info, value, force, true);
} }
void MySettings::setModelChatNamePrompt(const ModelInfo &info, const QString &value, bool force)
{
setModelSetting("chatNamePrompt", info, value, force, true);
}
void MySettings::setModelSuggestedFollowUpPrompt(const ModelInfo &info, const QString &value, bool force)
{
setModelSetting("suggestedFollowUpPrompt", info, value, force, true);
}
int MySettings::threadCount() const int MySettings::threadCount() const
{ {
int c = m_settings.value("threadCount", defaults::threadCount).toInt(); int c = m_settings.value("threadCount", defaults::threadCount).toInt();
@ -383,6 +399,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(); }
SuggestionMode MySettings::suggestionMode() const { return getBasicSetting("suggestionMode").value<SuggestionMode>(); };
void MySettings::setSaveChatsContext(bool value) { setBasicSetting("saveChatsContext", value); } void MySettings::setSaveChatsContext(bool value) { setBasicSetting("saveChatsContext", value); }
void MySettings::setServerChat(bool value) { setBasicSetting("serverChat", value); } void MySettings::setServerChat(bool value) { setBasicSetting("serverChat", value); }
@ -399,6 +416,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::setSuggestionMode(SuggestionMode value) { setBasicSetting("suggestionMode", int(value)); }
QString MySettings::modelPath() QString MySettings::modelPath()
{ {

View File

@ -13,6 +13,18 @@
#include <cstdint> #include <cstdint>
#include <optional> #include <optional>
namespace MySettingsEnums {
Q_NAMESPACE
enum class SuggestionMode {
LocalDocsOnly = 0,
On = 1,
Off = 2,
};
Q_ENUM_NS(SuggestionMode)
}
using namespace MySettingsEnums;
class MySettings : public QObject class MySettings : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -39,6 +51,7 @@ class MySettings : public QObject
Q_PROPERTY(QStringList deviceList MEMBER m_deviceList CONSTANT) Q_PROPERTY(QStringList deviceList MEMBER m_deviceList CONSTANT)
Q_PROPERTY(QStringList embeddingsDeviceList MEMBER m_embeddingsDeviceList CONSTANT) Q_PROPERTY(QStringList embeddingsDeviceList MEMBER m_embeddingsDeviceList CONSTANT)
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)
public: public:
static MySettings *globalInstance(); static MySettings *globalInstance();
@ -98,6 +111,10 @@ public:
Q_INVOKABLE void setModelContextLength(const ModelInfo &info, int value, bool force = false); Q_INVOKABLE void setModelContextLength(const ModelInfo &info, int value, bool force = false);
int modelGpuLayers(const ModelInfo &info) const; int modelGpuLayers(const ModelInfo &info) const;
Q_INVOKABLE void setModelGpuLayers(const ModelInfo &info, int value, bool force = false); Q_INVOKABLE void setModelGpuLayers(const ModelInfo &info, int value, bool force = false);
QString modelChatNamePrompt(const ModelInfo &info) const;
Q_INVOKABLE void setModelChatNamePrompt(const ModelInfo &info, const QString &value, bool force = false);
QString modelSuggestedFollowUpPrompt(const ModelInfo &info) const;
Q_INVOKABLE void setModelSuggestedFollowUpPrompt(const ModelInfo &info, const QString &value, bool force = false);
// Application settings // Application settings
int threadCount() const; int threadCount() const;
@ -122,6 +139,8 @@ public:
void setContextLength(int32_t value); void setContextLength(int32_t value);
int32_t gpuLayers() const; int32_t gpuLayers() const;
void setGpuLayers(int32_t value); void setGpuLayers(int32_t value);
SuggestionMode suggestionMode() const;
void setSuggestionMode(SuggestionMode mode);
// Release/Download settings // Release/Download settings
QString lastVersionStarted() const; QString lastVersionStarted() const;
@ -171,6 +190,8 @@ Q_SIGNALS:
void repeatPenaltyTokensChanged(const ModelInfo &info); void repeatPenaltyTokensChanged(const ModelInfo &info);
void promptTemplateChanged(const ModelInfo &info); void promptTemplateChanged(const ModelInfo &info);
void systemPromptChanged(const ModelInfo &info); void systemPromptChanged(const ModelInfo &info);
void chatNamePromptChanged(const ModelInfo &info);
void suggestedFollowUpPromptChanged(const ModelInfo &info);
void threadCountChanged(); void threadCountChanged();
void saveChatsContextChanged(); void saveChatsContextChanged();
void serverChatChanged(); void serverChatChanged();
@ -193,6 +214,7 @@ Q_SIGNALS:
void networkUsageStatsActiveChanged(); void networkUsageStatsActiveChanged();
void attemptModelLoadChanged(); void attemptModelLoadChanged();
void deviceChanged(); void deviceChanged();
void suggestionModeChanged();
private: private:
QSettings m_settings; QSettings m_settings;

View File

@ -227,16 +227,40 @@ MySettingsTab {
MySettings.userDefaultModel = comboBox.currentText MySettings.userDefaultModel = comboBox.currentText
} }
} }
MySettingsLabel {
id: suggestionModeLabel
text: qsTr("Suggestion Mode")
helpText: qsTr("Generate suggested follow-up questions at the end of responses.")
Layout.row: 6
Layout.column: 0
}
MyComboBox {
id: suggestionModeBox
Layout.row: 6
Layout.column: 2
Layout.minimumWidth: 400
Layout.maximumWidth: 400
Layout.alignment: Qt.AlignRight
model: [ qsTr("When chatting with LocalDocs"), qsTr("Whenever possible"), qsTr("Never") ]
Accessible.name: suggestionModeLabel.text
Accessible.description: suggestionModeLabel.helpText
onActivated: {
MySettings.suggestionMode = suggestionModeBox.currentIndex;
}
Component.onCompleted: {
suggestionModeBox.currentIndex = MySettings.suggestionMode;
}
}
MySettingsLabel { MySettingsLabel {
id: modelPathLabel id: modelPathLabel
text: qsTr("Download Path") text: qsTr("Download Path")
helpText: qsTr("Where to store local models and the LocalDocs database.") helpText: qsTr("Where to store local models and the LocalDocs database.")
Layout.row: 6 Layout.row: 7
Layout.column: 0 Layout.column: 0
} }
RowLayout { RowLayout {
Layout.row: 6 Layout.row: 7
Layout.column: 2 Layout.column: 2
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.minimumWidth: 400 Layout.minimumWidth: 400
@ -273,12 +297,12 @@ MySettingsTab {
id: dataLakeLabel id: dataLakeLabel
text: qsTr("Enable Datalake") text: qsTr("Enable Datalake")
helpText: qsTr("Send chats and feedback to the GPT4All Open-Source Datalake.") helpText: qsTr("Send chats and feedback to the GPT4All Open-Source Datalake.")
Layout.row: 7 Layout.row: 8
Layout.column: 0 Layout.column: 0
} }
MyCheckBox { MyCheckBox {
id: dataLakeBox id: dataLakeBox
Layout.row: 7 Layout.row: 8
Layout.column: 2 Layout.column: 2
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Component.onCompleted: { dataLakeBox.checked = MySettings.networkIsActive; } Component.onCompleted: { dataLakeBox.checked = MySettings.networkIsActive; }
@ -296,7 +320,7 @@ MySettingsTab {
} }
ColumnLayout { ColumnLayout {
Layout.row: 8 Layout.row: 9
Layout.column: 0 Layout.column: 0
Layout.columnSpan: 3 Layout.columnSpan: 3
Layout.fillWidth: true Layout.fillWidth: true
@ -319,7 +343,7 @@ MySettingsTab {
id: nThreadsLabel id: nThreadsLabel
text: qsTr("CPU Threads") text: qsTr("CPU Threads")
helpText: qsTr("The number of CPU threads used for inference and embedding.") helpText: qsTr("The number of CPU threads used for inference and embedding.")
Layout.row: 9 Layout.row: 10
Layout.column: 0 Layout.column: 0
} }
MyTextField { MyTextField {
@ -327,7 +351,7 @@ MySettingsTab {
color: theme.textColor color: theme.textColor
font.pixelSize: theme.fontSizeLarge font.pixelSize: theme.fontSizeLarge
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.row: 9 Layout.row: 10
Layout.column: 2 Layout.column: 2
Layout.minimumWidth: 200 Layout.minimumWidth: 200
Layout.maximumWidth: 200 Layout.maximumWidth: 200
@ -351,12 +375,12 @@ MySettingsTab {
id: saveChatsContextLabel id: saveChatsContextLabel
text: qsTr("Save Chat Context") text: qsTr("Save Chat Context")
helpText: qsTr("Save the chat model's state to disk for faster loading. WARNING: Uses ~2GB per chat.") helpText: qsTr("Save the chat model's state to disk for faster loading. WARNING: Uses ~2GB per chat.")
Layout.row: 10 Layout.row: 11
Layout.column: 0 Layout.column: 0
} }
MyCheckBox { MyCheckBox {
id: saveChatsContextBox id: saveChatsContextBox
Layout.row: 10 Layout.row: 11
Layout.column: 2 Layout.column: 2
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: MySettings.saveChatsContext checked: MySettings.saveChatsContext
@ -368,12 +392,12 @@ MySettingsTab {
id: serverChatLabel id: serverChatLabel
text: qsTr("Enable Local Server") text: qsTr("Enable Local Server")
helpText: qsTr("Expose an OpenAI-Compatible server to localhost. WARNING: Results in increased resource usage.") helpText: qsTr("Expose an OpenAI-Compatible server to localhost. WARNING: Results in increased resource usage.")
Layout.row: 11 Layout.row: 12
Layout.column: 0 Layout.column: 0
} }
MyCheckBox { MyCheckBox {
id: serverChatBox id: serverChatBox
Layout.row: 11 Layout.row: 12
Layout.column: 2 Layout.column: 2
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: MySettings.serverChat checked: MySettings.serverChat
@ -385,7 +409,7 @@ MySettingsTab {
id: serverPortLabel id: serverPortLabel
text: qsTr("API Server Port") text: qsTr("API Server Port")
helpText: qsTr("The port to use for the local server. Requires restart.") helpText: qsTr("The port to use for the local server. Requires restart.")
Layout.row: 12 Layout.row: 13
Layout.column: 0 Layout.column: 0
} }
MyTextField { MyTextField {
@ -393,7 +417,7 @@ MySettingsTab {
text: MySettings.networkPort text: MySettings.networkPort
color: theme.textColor color: theme.textColor
font.pixelSize: theme.fontSizeLarge font.pixelSize: theme.fontSizeLarge
Layout.row: 12 Layout.row: 13
Layout.column: 2 Layout.column: 2
Layout.minimumWidth: 200 Layout.minimumWidth: 200
Layout.maximumWidth: 200 Layout.maximumWidth: 200

View File

@ -797,7 +797,7 @@ Rectangle {
delegate: GridLayout { delegate: GridLayout {
width: listView.contentItem.width - 15 width: listView.contentItem.width - 15
rows: 3 rows: 5
columns: 2 columns: 2
Item { Item {
@ -850,6 +850,8 @@ Rectangle {
font.pixelSize: theme.fontSizeLarger font.pixelSize: theme.fontSizeLarger
font.bold: true font.bold: true
color: theme.conversationHeader color: theme.conversationHeader
enabled: false
focus: false
readOnly: true readOnly: true
} }
Text { Text {
@ -872,6 +874,7 @@ Rectangle {
case Chat.LocalDocsProcessing: return qsTr("searching localdocs: ") + currentChat.collectionList.join(", ") + " ..."; case Chat.LocalDocsProcessing: return qsTr("searching localdocs: ") + currentChat.collectionList.join(", ") + " ...";
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 ...");
default: return ""; // handle unexpected values default: return ""; // handle unexpected values
} }
} }
@ -1094,7 +1097,16 @@ Rectangle {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: childrenRect.width Layout.preferredWidth: childrenRect.width
Layout.preferredHeight: childrenRect.height Layout.preferredHeight: childrenRect.height
visible: consolidatedSources.length !== 0 && MySettings.localDocsShowReferences && (!currentResponse || !currentChat.responseInProgress) visible: {
if (consolidatedSources.length === 0)
return false
if (!MySettings.localDocsShowReferences)
return false
if (currentResponse && currentChat.responseInProgress
&& currentChat.responseState !== Chat.GeneratingQuestions )
return false
return true
}
MyButton { MyButton {
backgroundColor: theme.sourcesBackground backgroundColor: theme.sourcesBackground
@ -1171,7 +1183,16 @@ Rectangle {
Layout.row: 3 Layout.row: 3
Layout.column: 1 Layout.column: 1
Layout.topMargin: 5 Layout.topMargin: 5
visible: consolidatedSources.length !== 0 && MySettings.localDocsShowReferences && (!currentResponse || !currentChat.responseInProgress) visible: {
if (consolidatedSources.length === 0)
return false
if (!MySettings.localDocsShowReferences)
return false
if (currentResponse && currentChat.responseInProgress
&& currentChat.responseState !== Chat.GeneratingQuestions )
return false
return true
}
clip: true clip: true
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 0 Layout.preferredHeight: 0
@ -1310,45 +1331,250 @@ Rectangle {
} }
} }
} }
function shouldShowSuggestions() {
if (!currentResponse)
return false;
if (MySettings.suggestionMode === 2) // Off
return false;
if (MySettings.suggestionMode === 0 && consolidatedSources.length === 0) // LocalDocs only
return false;
return currentChat.responseState === Chat.GeneratingQuestions || currentChat.generatedQuestions.length !== 0;
} }
property bool shouldAutoScroll: true Item {
property bool isAutoScrolling: false visible: shouldShowSuggestions()
Layout.row: 4
Layout.column: 0
Layout.topMargin: 20
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.preferredWidth: 28
Layout.preferredHeight: 28
Image {
id: stack
sourceSize: Qt.size(28, 28)
fillMode: Image.PreserveAspectFit
mipmap: true
visible: false
source: "qrc:/gpt4all/icons/stack.svg"
}
Connections { ColorOverlay {
target: currentChat anchors.fill: stack
function onResponseChanged() { source: stack
listView.scrollToEnd() color: theme.conversationHeader
}
}
Item {
visible: shouldShowSuggestions()
Layout.row: 4
Layout.column: 1
Layout.topMargin: 20
Layout.fillWidth: true
Layout.preferredHeight: 38
RowLayout {
spacing: 5
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
TextArea {
text: qsTr("Suggested follow-ups")
padding: 0
font.pixelSize: theme.fontSizeLarger
font.bold: true
color: theme.conversationHeader
enabled: false
focus: false
readOnly: true
}
}
}
ColumnLayout {
visible: shouldShowSuggestions()
Layout.row: 5
Layout.column: 1
Layout.fillWidth: true
Layout.minimumHeight: 1
spacing: 10
Repeater {
model: currentChat.generatedQuestions
TextArea {
id: followUpText
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
rightPadding: 40
topPadding: 10
leftPadding: 20
bottomPadding: 10
text: modelData
focus: false
readOnly: true
wrapMode: Text.WordWrap
hoverEnabled: !currentChat.responseInProgress
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
background: Rectangle {
color: hovered ? theme.sourcesBackgroundHovered : theme.sourcesBackground
radius: 10
}
MouseArea {
id: maFollowUp
anchors.fill: parent
enabled: !currentChat.responseInProgress
onClicked: function() {
var chat = window.currentChat
var followup = modelData
chat.stopGenerating()
chat.newPromptResponsePair(followup);
chat.prompt(followup,
MySettings.promptTemplate,
MySettings.maxLength,
MySettings.topK,
MySettings.topP,
MySettings.minP,
MySettings.temperature,
MySettings.promptBatchSize,
MySettings.repeatPenalty,
MySettings.repeatPenaltyTokens)
}
}
Item {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: 40
height: 40
visible: !currentChat.responseInProgress
Image {
id: plusImage
anchors.verticalCenter: parent.verticalCenter
sourceSize.width: 20
sourceSize.height: 20
mipmap: true
visible: false
source: "qrc:/gpt4all/icons/plus.svg"
}
ColorOverlay {
anchors.fill: plusImage
source: plusImage
color: theme.styledTextColor
}
}
}
}
Rectangle {
Layout.fillWidth: true
color: "transparent"
radius: 10
Layout.preferredHeight: currentChat.responseInProgress ? 40 : 0
clip: true
ColumnLayout {
id: followUpLayout
anchors.fill: parent
Rectangle {
id: myRect1
Layout.preferredWidth: 0
Layout.minimumWidth: 0
Layout.maximumWidth: parent.width
height: 12
color: theme.sourcesBackgroundHovered
}
Rectangle {
id: myRect2
Layout.preferredWidth: 0
Layout.minimumWidth: 0
Layout.maximumWidth: parent.width
height: 12
color: theme.sourcesBackgroundHovered
}
SequentialAnimation {
id: followUpProgressAnimation
ParallelAnimation {
PropertyAnimation {
target: myRect1
property: "Layout.preferredWidth"
from: 0
to: followUpLayout.width
duration: 1000
}
PropertyAnimation {
target: myRect2
property: "Layout.preferredWidth"
from: 0
to: followUpLayout.width / 2
duration: 1000
}
}
SequentialAnimation {
loops: Animation.Infinite
ParallelAnimation {
PropertyAnimation {
target: myRect1
property: "opacity"
from: 1
to: 0.2
duration: 1500
}
PropertyAnimation {
target: myRect2
property: "opacity"
from: 1
to: 0.2
duration: 1500
}
}
ParallelAnimation {
PropertyAnimation {
target: myRect1
property: "opacity"
from: 0.2
to: 1
duration: 1500
}
PropertyAnimation {
target: myRect2
property: "opacity"
from: 0.2
to: 1
duration: 1500
}
}
}
}
onVisibleChanged: {
if (visible)
followUpProgressAnimation.start();
}
}
Behavior on Layout.preferredHeight {
NumberAnimation {
duration: 300
easing.type: Easing.InOutQuad
}
}
}
} }
} }
function scrollToEnd() { function scrollToEnd() {
if (listView.shouldAutoScroll) {
listView.isAutoScrolling = true
listView.positionViewAtEnd() listView.positionViewAtEnd()
listView.isAutoScrolling = false
}
} }
onContentYChanged: { onContentHeightChanged: {
if (!isAutoScrolling) if (atYEnd)
shouldAutoScroll = atYEnd scrollToEnd()
}
Component.onCompleted: {
shouldAutoScroll = true
positionViewAtEnd()
}
footer: Item {
id: bottomPadding
width: parent.width
height: 0
} }
} }
} }
} }
} }
Rectangle { Rectangle {

View File

@ -250,45 +250,64 @@ MySettingsTab {
} }
} }
MySettingsLabel {
id: chatNamePromptLabel
text: qsTr("Chat Name Prompt")
helpText: qsTr("Prompt used to automatically generate chat names.")
Layout.row: 11
Layout.column: 0
Layout.topMargin: 15
}
Rectangle { Rectangle {
id: optionalImageRect id: chatNamePrompt
visible: false // FIXME: for later Layout.row: 12
Layout.row: 2 Layout.column: 0
Layout.column: 1 Layout.columnSpan: 2
Layout.rowSpan: 5 Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter Layout.minimumHeight: Math.max(100, chatNamePromptTextArea.contentHeight + 20)
Layout.fillHeight: true
Layout.maximumWidth: height
Layout.topMargin: 35
Layout.bottomMargin: 35
Layout.leftMargin: 35
width: 3000
radius: 10
color: "transparent" color: "transparent"
Item { clip: true
anchors.centerIn: parent MyTextArea {
height: childrenRect.height id: chatNamePromptTextArea
Image { anchors.fill: parent
id: img text: root.currentModelInfo.chatNamePrompt
anchors.horizontalCenter: parent.horizontalCenter Accessible.role: Accessible.EditableText
width: 100 Accessible.name: chatNamePromptLabel.text
height: 100 Accessible.description: chatNamePromptLabel.text
source: "qrc:/gpt4all/icons/image.svg"
} }
Text {
text: qsTr("Add\noptional image")
font.pixelSize: theme.fontSizeLarge
anchors.top: img.bottom
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: TextArea.Wrap
horizontalAlignment: Qt.AlignHCenter
color: theme.mutedTextColor
} }
MySettingsLabel {
id: suggestedFollowUpPromptLabel
text: qsTr("Suggested FollowUp Prompt")
helpText: qsTr("Prompt used to generate suggested follow-up questions.")
Layout.row: 13
Layout.column: 0
Layout.topMargin: 15
}
Rectangle {
id: suggestedFollowUpPrompt
Layout.row: 14
Layout.column: 0
Layout.columnSpan: 2
Layout.fillWidth: true
Layout.minimumHeight: Math.max(100, suggestedFollowUpPromptTextArea.contentHeight + 20)
color: "transparent"
clip: true
MyTextArea {
id: suggestedFollowUpPromptTextArea
anchors.fill: parent
text: root.currentModelInfo.suggestedFollowUpPrompt
Accessible.role: Accessible.EditableText
Accessible.name: suggestedFollowUpPromptLabel.text
Accessible.description: suggestedFollowUpPromptLabel.text
} }
} }
GridLayout { GridLayout {
Layout.row: 11 Layout.row: 15
Layout.column: 0 Layout.column: 0
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.topMargin: 15 Layout.topMargin: 15
@ -784,7 +803,7 @@ MySettingsTab {
} }
Rectangle { Rectangle {
Layout.row: 12 Layout.row: 16
Layout.column: 0 Layout.column: 0
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.topMargin: 15 Layout.topMargin: 15