mirror of
https://github.com/nomic-ai/gpt4all.git
synced 2025-08-17 15:37:07 +00:00
Add new solution for context links that does not force regular markdown (#938)
in responses which is disruptive to code completions in responses.
This commit is contained in:
parent
d3ba1295a7
commit
a9c2f47303
@ -76,6 +76,7 @@ qt_add_executable(chat
|
|||||||
llm.h llm.cpp
|
llm.h llm.cpp
|
||||||
server.h server.cpp
|
server.h server.cpp
|
||||||
logger.h logger.cpp
|
logger.h logger.cpp
|
||||||
|
responsetext.h responsetext.cpp
|
||||||
sysinfo.h
|
sysinfo.h
|
||||||
${METAL_SHADER_FILE}
|
${METAL_SHADER_FILE}
|
||||||
)
|
)
|
||||||
|
@ -7,6 +7,7 @@ import Qt5Compat.GraphicalEffects
|
|||||||
import llm
|
import llm
|
||||||
import download
|
import download
|
||||||
import network
|
import network
|
||||||
|
import gpt4all
|
||||||
|
|
||||||
Window {
|
Window {
|
||||||
id: window
|
id: window
|
||||||
@ -580,11 +581,12 @@ Window {
|
|||||||
Accessible.description: qsTr("This is the list of prompt/response pairs comprising the actual conversation with the model")
|
Accessible.description: qsTr("This is the list of prompt/response pairs comprising the actual conversation with the model")
|
||||||
|
|
||||||
delegate: TextArea {
|
delegate: TextArea {
|
||||||
|
id: myTextArea
|
||||||
text: value + references
|
text: value + references
|
||||||
width: listView.width
|
width: listView.width
|
||||||
color: theme.textColor
|
color: theme.textColor
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
textFormat: TextEdit.MarkdownText
|
textFormat: TextEdit.PlainText
|
||||||
focus: false
|
focus: false
|
||||||
readOnly: true
|
readOnly: true
|
||||||
font.pixelSize: theme.fontSizeLarge
|
font.pixelSize: theme.fontSizeLarge
|
||||||
@ -597,6 +599,31 @@ Window {
|
|||||||
: (currentChat.isServer ? theme.backgroundDark : theme.backgroundLight)
|
: (currentChat.isServer ? theme.backgroundDark : theme.backgroundLight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
propagateComposedEvents: true
|
||||||
|
onClicked: {
|
||||||
|
var clickedPos = myTextArea.positionAt(mouse.x, mouse.y);
|
||||||
|
var link = responseText.getLinkAtPosition(clickedPos);
|
||||||
|
if (!link.startsWith("context://"))
|
||||||
|
return
|
||||||
|
var integer = parseInt(link.split("://")[1]);
|
||||||
|
referenceContextDialog.text = referencesContext[integer - 1];
|
||||||
|
referenceContextDialog.open();
|
||||||
|
mouse.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseText {
|
||||||
|
id: responseText
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
responseText.textDocument = textDocument
|
||||||
|
responseText.setLinkColor(theme.linkColor);
|
||||||
|
}
|
||||||
|
|
||||||
Accessible.role: Accessible.Paragraph
|
Accessible.role: Accessible.Paragraph
|
||||||
Accessible.name: name
|
Accessible.name: name
|
||||||
Accessible.description: name === qsTr("Response: ") ? "The response by the model" : "The prompt by the user"
|
Accessible.description: name === qsTr("Response: ") ? "The response by the model" : "The prompt by the user"
|
||||||
|
@ -76,7 +76,7 @@ Dialog {
|
|||||||
Label {
|
Label {
|
||||||
id: discordLink
|
id: discordLink
|
||||||
width: parent.width
|
width: parent.width
|
||||||
textFormat: Text.RichText
|
textFormat: Text.StyledText
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
text: qsTr("Check out our discord channel <a href=\"https://discord.gg/4M2QFmTt2k\">https://discord.gg/4M2QFmTt2k</a>")
|
text: qsTr("Check out our discord channel <a href=\"https://discord.gg/4M2QFmTt2k\">https://discord.gg/4M2QFmTt2k</a>")
|
||||||
onLinkActivated: { Qt.openUrlExternally("https://discord.gg/4M2QFmTt2k") }
|
onLinkActivated: { Qt.openUrlExternally("https://discord.gg/4M2QFmTt2k") }
|
||||||
@ -90,7 +90,7 @@ Dialog {
|
|||||||
Label {
|
Label {
|
||||||
id: nomicProps
|
id: nomicProps
|
||||||
width: parent.width
|
width: parent.width
|
||||||
textFormat: Text.RichText
|
textFormat: Text.StyledText
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
text: qsTr("Thank you to <a href=\"https://home.nomic.ai\">Nomic AI</a> and the community for contributing so much great data, code, ideas, and energy to the growing open source AI ecosystem!")
|
text: qsTr("Thank you to <a href=\"https://home.nomic.ai\">Nomic AI</a> and the community for contributing so much great data, code, ideas, and energy to the growing open source AI ecosystem!")
|
||||||
onLinkActivated: { Qt.openUrlExternally("https://home.nomic.ai") }
|
onLinkActivated: { Qt.openUrlExternally("https://home.nomic.ai") }
|
||||||
|
@ -18,8 +18,8 @@ QtObject {
|
|||||||
property color dialogBorder: "#d1d5db"
|
property color dialogBorder: "#d1d5db"
|
||||||
property color userColor: "#ec86bf"
|
property color userColor: "#ec86bf"
|
||||||
property color assistantColor: "#10a37f"
|
property color assistantColor: "#10a37f"
|
||||||
property color linkColor: "white"
|
property color linkColor: "#55aaff"
|
||||||
property color tabBorder: "#2C2D35"
|
property color tabBorder: "#2c2d35"
|
||||||
property real fontSizeLarge: Qt.application.font.pixelSize
|
property real fontSizeLarge: Qt.application.font.pixelSize
|
||||||
property real fontSizeLarger: Qt.application.font.pixelSize + 2
|
property real fontSizeLarger: Qt.application.font.pixelSize + 2
|
||||||
}
|
}
|
||||||
|
109
gpt4all-chat/responsetext.cpp
Normal file
109
gpt4all-chat/responsetext.cpp
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#include "responsetext.h"
|
||||||
|
|
||||||
|
#include <QTextCharFormat>
|
||||||
|
#include <QTextDocument>
|
||||||
|
#include <QTextDocumentFragment>
|
||||||
|
#include <QFontMetricsF>
|
||||||
|
#include <QTextTableCell>
|
||||||
|
|
||||||
|
SyntaxHighlighter::SyntaxHighlighter(QObject *parent)
|
||||||
|
: QSyntaxHighlighter(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SyntaxHighlighter::~SyntaxHighlighter()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyntaxHighlighter::highlightBlock(const QString &text)
|
||||||
|
{
|
||||||
|
for (const HighlightingRule &rule : qAsConst(m_highlightingRules)) {
|
||||||
|
QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
|
||||||
|
while (matchIterator.hasNext()) {
|
||||||
|
QRegularExpressionMatch match = matchIterator.next();
|
||||||
|
setFormat(match.capturedStart(), match.capturedLength(), rule.format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseText::ResponseText(QObject *parent)
|
||||||
|
: QObject{parent}
|
||||||
|
, m_textDocument(nullptr)
|
||||||
|
, m_syntaxHighlighter(new SyntaxHighlighter(this))
|
||||||
|
, m_isProcessingText(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QQuickTextDocument* ResponseText::textDocument() const
|
||||||
|
{
|
||||||
|
return m_textDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResponseText::setTextDocument(QQuickTextDocument* textDocument)
|
||||||
|
{
|
||||||
|
if (m_textDocument)
|
||||||
|
disconnect(m_textDocument->textDocument(), &QTextDocument::contentsChanged, this, &ResponseText::handleTextChanged);
|
||||||
|
|
||||||
|
m_textDocument = textDocument;
|
||||||
|
m_syntaxHighlighter->setDocument(m_textDocument->textDocument());
|
||||||
|
connect(m_textDocument->textDocument(), &QTextDocument::contentsChanged, this, &ResponseText::handleTextChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ResponseText::getLinkAtPosition(int position) const
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
for (const auto &link : m_links) {
|
||||||
|
if (position >= link.startPos && position < link.endPos)
|
||||||
|
return link.href;
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResponseText::handleTextChanged()
|
||||||
|
{
|
||||||
|
if (!m_textDocument || m_isProcessingText)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_isProcessingText = true;
|
||||||
|
QTextDocument* doc = m_textDocument->textDocument();
|
||||||
|
QTextCursor cursor(doc);
|
||||||
|
|
||||||
|
QTextCharFormat linkFormat;
|
||||||
|
linkFormat.setForeground(m_linkColor);
|
||||||
|
linkFormat.setFontUnderline(true);
|
||||||
|
|
||||||
|
// Loop through the document looking for context links
|
||||||
|
QRegularExpression re("\\[Context\\]\\((context://\\d+)\\)");
|
||||||
|
QRegularExpressionMatchIterator i = re.globalMatch(doc->toPlainText());
|
||||||
|
|
||||||
|
QList<QRegularExpressionMatch> matches;
|
||||||
|
while (i.hasNext())
|
||||||
|
matches.append(i.next());
|
||||||
|
|
||||||
|
QVector<ContextLink> newLinks;
|
||||||
|
|
||||||
|
// Calculate new positions and store them in newLinks
|
||||||
|
int positionOffset = 0;
|
||||||
|
for(const auto &match : matches) {
|
||||||
|
ContextLink newLink;
|
||||||
|
newLink.href = match.captured(1);
|
||||||
|
newLink.text = "Context";
|
||||||
|
newLink.startPos = match.capturedStart() - positionOffset;
|
||||||
|
newLink.endPos = newLink.startPos + newLink.text.length();
|
||||||
|
newLinks.append(newLink);
|
||||||
|
positionOffset += match.capturedLength() - newLink.text.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the context links with the word "Context" in reverse order
|
||||||
|
for(int index = matches.count() - 1; index >= 0; --index) {
|
||||||
|
cursor.setPosition(matches.at(index).capturedStart());
|
||||||
|
cursor.setPosition(matches.at(index).capturedEnd(), QTextCursor::KeepAnchor);
|
||||||
|
cursor.removeSelectedText();
|
||||||
|
cursor.setCharFormat(linkFormat);
|
||||||
|
cursor.insertText(newLinks.at(index).text);
|
||||||
|
cursor.setCharFormat(QTextCharFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_links = newLinks;
|
||||||
|
m_isProcessingText = false;
|
||||||
|
}
|
63
gpt4all-chat/responsetext.h
Normal file
63
gpt4all-chat/responsetext.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#ifndef RESPONSETEXT_H
|
||||||
|
#define RESPONSETEXT_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QQuickTextDocument>
|
||||||
|
#include <QSyntaxHighlighter>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
struct HighlightingRule
|
||||||
|
{
|
||||||
|
QRegularExpression pattern;
|
||||||
|
QTextCharFormat format;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SyntaxHighlighter : public QSyntaxHighlighter {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
SyntaxHighlighter(QObject *parent);
|
||||||
|
~SyntaxHighlighter();
|
||||||
|
|
||||||
|
void highlightBlock(const QString &text) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QVector<HighlightingRule> m_highlightingRules;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ContextLink {
|
||||||
|
int startPos = -1;
|
||||||
|
int endPos = -1;
|
||||||
|
QString text;
|
||||||
|
QString href;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ResponseText : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QQuickTextDocument* textDocument READ textDocument WRITE setTextDocument NOTIFY textDocumentChanged())
|
||||||
|
QML_ELEMENT
|
||||||
|
public:
|
||||||
|
explicit ResponseText(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
QQuickTextDocument* textDocument() const;
|
||||||
|
void setTextDocument(QQuickTextDocument* textDocument);
|
||||||
|
|
||||||
|
Q_INVOKABLE void setLinkColor(const QColor &c) { m_linkColor = c; }
|
||||||
|
Q_INVOKABLE QString getLinkAtPosition(int position) const;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void textDocumentChanged();
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void handleTextChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QQuickTextDocument *m_textDocument;
|
||||||
|
SyntaxHighlighter *m_syntaxHighlighter;
|
||||||
|
QVector<ContextLink> m_links;
|
||||||
|
QColor m_linkColor;
|
||||||
|
bool m_isProcessingText = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RESPONSETEXT_H
|
Loading…
Reference in New Issue
Block a user