Compare commits

...

25 Commits
v0.5.9 ... main

Author SHA1 Message Date
Petr Mironychev
af3fdb58ff fix: Add custom endpoint to reset function 2025-05-18 20:06:46 +02:00
Petr Mironychev
637a4d9d4c
feat: Add custom providers endpoint (#188) 2025-05-17 09:21:06 +02:00
Petr Mironychev
7e2345773f
doc: Add support QtC 16.0.2 to README.md 2025-05-14 19:20:35 +02:00
Petr Mironychev
14a5ddbdd8 chore: Upgrade plugin to 0.5.13 2025-05-14 19:09:16 +02:00
Petr Mironychev
e178b7daa7
feat: Add multi QtCreator versions
* feat: Add multi qtc version
* feat Upgrade plugin to QtC 16.0.2
2025-05-14 16:06:15 +02:00
Petr Mironychev
4b353d5091
doc: Update targets in README.md 2025-05-10 15:09:35 +02:00
Petr Mironychev
f7ba7b95be
doc: Fix ignore files api in README.md 2025-05-02 08:58:31 +02:00
Petr Mironychev
6ae95fec45
doc: Added quick refactoring feature description to README.md 2025-05-02 08:31:34 +02:00
Petr Mironychev
dad8ab2bf3 chore: Update plugin to 0.5.12 2025-05-01 15:38:05 +02:00
Petr Mironychev
25a6983de0
refactor: Make connection more async (#182) 2025-05-01 15:35:33 +02:00
Petr Mironychev
4e05abc7d2 feat: Add settings for text format 2025-05-01 00:01:44 +02:00
Petr Mironychev
784529e344
feat: Add chat font settings (#180) 2025-04-30 22:44:59 +02:00
Petr Mironychev
155153a763 fix: Optimize searching unreadable symbols for markdown 2025-04-30 21:23:43 +02:00
Petr Mironychev
9225c0c1a9 fix: Check readable symbols for markdown 2025-04-29 21:55:44 +02:00
Petr Mironychev
43adc95857 feat: Add a floating "copy" button 2025-04-28 09:25:39 +02:00
Petr Mironychev
ee672f2cda refactor: Remove Chat preview scrollbar 2025-04-26 17:29:06 +02:00
Petr Mironychev
a3edb8a577 chore: Upgrade plugin to 0.5.11 2025-04-24 21:46:32 +02:00
Petr Mironychev
407d3b11c0 fix: Change maximum limit of chat tokens 2025-04-24 21:44:49 +02:00
Petr Mironychev
285e739074 refactor: Change base text style render to markdown 2025-04-24 21:38:54 +02:00
Petr Mironychev
f7e748ba7e chore: Upgrade plugin to 0.5.10 2025-04-24 03:24:00 +02:00
Petr Mironychev
acb1306321 fix: Improve detect unclose codeblock 2025-04-24 03:21:19 +02:00
Petr Mironychev
8b38ecc29b feat: Add Chat preview scroll bar 2025-04-24 02:54:21 +02:00
Petr Mironychev
cfb364f033 fix: Correct removing latest item in messages list 2025-04-24 01:32:01 +02:00
Petr Mironychev
2fe6850a06 refactor: Improve textsuggestion working 2025-04-24 01:25:45 +02:00
Petr Mironychev
3e9506ca92
doc: Add description of ignoring files feature to README.md 2025-04-21 09:40:34 +02:00
28 changed files with 692 additions and 156 deletions

View File

@ -13,7 +13,7 @@
"Linux"
],
"license": "GPLv3",
"version": "0.5.8",
"version": "0.5.11",
"status": "draft",
"is_pack": false,
"released_at": null,
@ -55,8 +55,28 @@
},
{
"version": "0.5.8",
"is_latest": true,
"is_latest": false,
"released_at": "2025-04-17T10:00:00Z"
},
{
"version": "0.5.9",
"is_latest": false,
"released_at": "2025-04-21T10:00:00Z"
},
{
"version": "0.5.10",
"is_latest": false,
"released_at": "2025-04-24T10:00:00Z"
},
{
"version": "0.5.11",
"is_latest": false,
"released_at": "2025-04-24T21:00:00Z"
},
{
"version": "0.5.12",
"is_latest": true,
"released_at": "2025-05-01T17:00:00Z"
}
],
"icon": "https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d",

View File

@ -12,16 +12,13 @@ on:
env:
PLUGIN_NAME: QodeAssist
QT_VERSION: 6.8.3
QT_CREATOR_VERSION: 16.0.1
QT_CREATOR_VERSION_INTERNAL: 16.0.1
MACOS_DEPLOYMENT_TARGET: "11.0"
CMAKE_VERSION: "3.29.6"
NINJA_VERSION: "1.12.1"
jobs:
build:
name: ${{ matrix.config.name }}
name: ${{ matrix.config.name }} (Qt ${{ matrix.qt_config.qt_version }}, QtC ${{ matrix.qt_config.qt_creator_version }})
runs-on: ${{ matrix.config.os }}
outputs:
tag: ${{ steps.git.outputs.tag }}
@ -47,12 +44,18 @@ jobs:
platform: mac_x64,
cc: "clang", cxx: "clang++"
}
qt_config:
- {
qt_version: "6.8.3",
qt_creator_version: "16.0.2"
}
- {
qt_version: "6.8.3",
qt_creator_version: "16.0.1"
}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Checkout submodules
id: git
@ -61,7 +64,12 @@ jobs:
if (${{github.ref}} MATCHES "tags/v(.*)")
file(APPEND "$ENV{GITHUB_OUTPUT}" "tag=${CMAKE_MATCH_1}")
else()
file(APPEND "$ENV{GITHUB_OUTPUT}" "tag=${{github.run_id}}")
execute_process(
COMMAND git rev-parse --short HEAD
OUTPUT_VARIABLE short_sha
OUTPUT_STRIP_TRAILING_WHITESPACE
)
file(APPEND "$ENV{GITHUB_OUTPUT}" "tag=${short_sha}")
endif()
- name: Download Ninja and CMake
@ -96,7 +104,7 @@ jobs:
id: qt
shell: cmake -P {0}
run: |
set(qt_version "$ENV{QT_VERSION}")
set(qt_version "${{ matrix.qt_config.qt_version }}")
string(REPLACE "." "" qt_version_dotless "${qt_version}")
if ("${{ runner.os }}" STREQUAL "Windows")
@ -174,10 +182,11 @@ jobs:
endif()
- name: Download Qt Creator
uses: qt-creator/install-dev-package@v1.2
uses: qt-creator/install-dev-package@v2.0
with:
version: ${{ env.QT_CREATOR_VERSION }}
version: ${{ matrix.qt_config.qt_creator_version }}
unzip-to: 'qtcreator'
platform: ${{ matrix.config.platform }}
- name: Extract Qt Creator
id: qt_creator
@ -223,7 +232,7 @@ jobs:
COMMAND python
-u
"${{ steps.qt_creator.outputs.qtc_dir }}/${build_plugin_py}"
--name "$ENV{PLUGIN_NAME}-v${{ steps.git.outputs.tag }}-QtC$ENV{QT_CREATOR_VERSION}-${{ matrix.config.artifact }}"
--name "$ENV{PLUGIN_NAME}-v${{ steps.git.outputs.tag }}-QtC${{ matrix.qt_config.qt_creator_version }}-${{ matrix.config.artifact }}"
--src .
--build build
--qt-path "${{ steps.qt.outputs.qt_dir }}"
@ -241,68 +250,18 @@ jobs:
- name: Upload
uses: actions/upload-artifact@v4
with:
path: ./${{ env.PLUGIN_NAME }}-v${{ steps.git.outputs.tag }}-QtC${{ env.QT_CREATOR_VERSION }}-${{ matrix.config.artifact }}.7z
name: ${{ env.PLUGIN_NAME}}-v${{ steps.git.outputs.tag }}-QtC${{ env.QT_CREATOR_VERSION }}-${{ matrix.config.artifact }}.7z
# The json is the same for all platforms, but we need to save one
- name: Upload plugin json
if: startsWith(matrix.config.os, 'ubuntu')
uses: actions/upload-artifact@v4
with:
name: ${{ env.PLUGIN_NAME }}-origin-json
path: ./build/build/${{ env.PLUGIN_NAME }}.json
path: ./${{ env.PLUGIN_NAME }}-v${{ steps.git.outputs.tag }}-QtC${{ matrix.qt_config.qt_creator_version }}-${{ matrix.config.artifact }}.7z
name: ${{ env.PLUGIN_NAME}}-v${{ steps.git.outputs.tag }}-QtC${{ matrix.qt_config.qt_creator_version }}-${{ matrix.config.artifact }}.7z
- name: Run unit tests
if: startsWith(matrix.config.os, 'ubuntu')
run: |
xvfb-run ./build/build/test/QodeAssistTest
update_json:
if: contains(github.ref, 'tags/v')
runs-on: ubuntu-22.04
needs: build
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Download the JSON file
uses: actions/download-artifact@v4
with:
name: ${{ env.PLUGIN_NAME }}-origin-json
path: ./${{ env.PLUGIN_NAME }}-origin
- name: Store Release upload_url
run: |
RELEASE_HTML_URL=$(echo "${{github.event.repository.html_url}}/releases/download/v${{ needs.build.outputs.tag }}")
echo "RELEASE_HTML_URL=${RELEASE_HTML_URL}" >> $GITHUB_ENV
- name: Run the Node.js script to update JSON
env:
QT_TOKEN: ${{ secrets.TOKEN }}
API_URL: ${{ secrets.API_URL }}
run: |
node .github/scripts/registerPlugin.js ${{ env.RELEASE_HTML_URL }} ${{ env.PLUGIN_NAME }} ${{ env.QT_CREATOR_VERSION }} ${{ env.QT_CREATOR_VERSION_INTERNAL }} ${{ env.QT_TOKEN }} ${{ env.API_URL }}
- name: Delete previous json artifacts
uses: geekyeggo/delete-artifact@v5
with:
name: ${{ env.PLUGIN_NAME }}*-json
- name: Upload the modified JSON file as an artifact
uses: actions/upload-artifact@v4
with:
name: plugin-json
path: .github/scripts/${{ env.PLUGIN_NAME }}.json
release:
if: contains(github.ref, 'tags/v')
runs-on: ubuntu-latest
needs: [build, update_json]
runs-on: ubuntu-22.04
needs: [build]
steps:
- name: Download artifacts

View File

@ -17,6 +17,7 @@ qt_add_qml_module(QodeAssistChatView
qml/parts/TopBar.qml
qml/parts/BottomBar.qml
qml/parts/AttachedFilesPlace.qml
RESOURCES
icons/attach-file-light.svg
icons/attach-file-dark.svg

View File

@ -124,6 +124,7 @@ QList<MessagePart> ChatModel::processMessageContent(const QString &content) cons
QRegularExpression codeBlockRegex("```(\\w*)\\n?([\\s\\S]*?)```");
int lastIndex = 0;
auto blockMatches = codeBlockRegex.globalMatch(content);
bool foundCodeBlock = blockMatches.hasNext();
while (blockMatches.hasNext()) {
auto match = blockMatches.next();
@ -140,7 +141,19 @@ QList<MessagePart> ChatModel::processMessageContent(const QString &content) cons
if (lastIndex < content.length()) {
QString remainingText = content.mid(lastIndex).trimmed();
if (!remainingText.isEmpty()) {
QRegularExpression unclosedBlockRegex("```(\\w*)\\n?([\\s\\S]*)$");
auto unclosedMatch = unclosedBlockRegex.match(remainingText);
if (unclosedMatch.hasMatch()) {
QString beforeCodeBlock = remainingText.left(unclosedMatch.capturedStart()).trimmed();
if (!beforeCodeBlock.isEmpty()) {
parts.append({MessagePart::Text, beforeCodeBlock, ""});
}
parts.append(
{MessagePart::Code, unclosedMatch.captured(2).trimmed(), unclosedMatch.captured(1)});
} else if (!remainingText.isEmpty()) {
parts.append({MessagePart::Text, remainingText, ""});
}
}
@ -202,7 +215,7 @@ void ChatModel::resetModelTo(int index)
if (index < 0 || index >= m_messages.size())
return;
if (index < m_messages.size() - 1) {
if (index < m_messages.size()) {
beginRemoveRows(QModelIndex(), index, m_messages.size() - 1);
m_messages.remove(index, m_messages.size() - index);
endRemoveRows();

View File

@ -102,6 +102,31 @@ ChatRootView::ChatRootView(QQuickItem *parent)
}
}
});
connect(
&Settings::chatAssistantSettings().textFontFamily,
&Utils::BaseAspect::changed,
this,
&ChatRootView::textFamilyChanged);
connect(
&Settings::chatAssistantSettings().codeFontFamily,
&Utils::BaseAspect::changed,
this,
&ChatRootView::codeFamilyChanged);
connect(
&Settings::chatAssistantSettings().textFontSize,
&Utils::BaseAspect::changed,
this,
&ChatRootView::textFontSizeChanged);
connect(
&Settings::chatAssistantSettings().codeFontSize,
&Utils::BaseAspect::changed,
this,
&ChatRootView::codeFontSizeChanged);
connect(
&Settings::chatAssistantSettings().textFormat,
&Utils::BaseAspect::changed,
this,
&ChatRootView::textFormatChanged);
updateInputTokensCount();
}
@ -552,4 +577,29 @@ bool ChatRootView::shouldIgnoreFileForAttach(const Utils::FilePath &filePath)
return false;
}
QString ChatRootView::textFontFamily() const
{
return Settings::chatAssistantSettings().textFontFamily.stringValue();
}
QString ChatRootView::codeFontFamily() const
{
return Settings::chatAssistantSettings().codeFontFamily.stringValue();
}
int ChatRootView::codeFontSize() const
{
return Settings::chatAssistantSettings().codeFontSize();
}
int ChatRootView::textFontSize() const
{
return Settings::chatAssistantSettings().textFontSize();
}
int ChatRootView::textFormat() const
{
return Settings::chatAssistantSettings().textFormat();
}
} // namespace QodeAssist::Chat

View File

@ -38,6 +38,11 @@ class ChatRootView : public QQuickItem
Q_PROPERTY(QStringList linkedFiles READ linkedFiles NOTIFY linkedFilesChanged FINAL)
Q_PROPERTY(int inputTokensCount READ inputTokensCount NOTIFY inputTokensCountChanged FINAL)
Q_PROPERTY(QString chatFileName READ chatFileName NOTIFY chatFileNameChanged FINAL)
Q_PROPERTY(QString textFontFamily READ textFontFamily NOTIFY textFamilyChanged FINAL)
Q_PROPERTY(QString codeFontFamily READ codeFontFamily NOTIFY codeFamilyChanged FINAL)
Q_PROPERTY(int codeFontSize READ codeFontSize NOTIFY codeFontSizeChanged FINAL)
Q_PROPERTY(int textFontSize READ textFontSize NOTIFY textFontSizeChanged FINAL)
Q_PROPERTY(int textFormat READ textFormat NOTIFY textFormatChanged FINAL)
QML_ELEMENT
@ -80,6 +85,13 @@ public:
void setRecentFilePath(const QString &filePath);
bool shouldIgnoreFileForAttach(const Utils::FilePath &filePath);
QString textFontFamily() const;
QString codeFontFamily() const;
int codeFontSize() const;
int textFontSize() const;
int textFormat() const;
public slots:
void sendMessage(const QString &message);
void copyToClipboard(const QString &text);
@ -95,6 +107,11 @@ signals:
void inputTokensCountChanged();
void isSyncOpenFilesChanged();
void chatFileNameChanged();
void textFamilyChanged();
void codeFamilyChanged();
void codeFontSizeChanged();
void textFontSizeChanged();
void textFormatChanged();
private:
QString getChatsHistoryDir() const;

View File

@ -29,4 +29,40 @@ void ChatUtils::copyToClipboard(const QString &text)
QGuiApplication::clipboard()->setText(text);
}
QString ChatUtils::getSafeMarkdownText(const QString &text) const
{
if (text.isEmpty()) {
return text;
}
bool needsSanitization = false;
for (const QChar &ch : text) {
if (ch.isNull() || (!ch.isPrint() && ch != '\n' && ch != '\t' && ch != '\r' && ch != ' ')) {
needsSanitization = true;
break;
}
}
if (!needsSanitization) {
return text;
}
QString safeText;
safeText.reserve(text.size());
for (QChar ch : text) {
if (ch.isNull()) {
safeText.append(' ');
} else if (ch == '\n' || ch == '\t' || ch == '\r' || ch == ' ') {
safeText.append(ch);
} else if (ch.isPrint()) {
safeText.append(ch);
} else {
safeText.append(QChar(0xFFFD));
}
}
return safeText;
}
} // namespace QodeAssist::Chat

View File

@ -34,6 +34,7 @@ public:
: QObject(parent) {};
Q_INVOKABLE void copyToClipboard(const QString &text);
Q_INVOKABLE QString getSafeMarkdownText(const QString &text) const;
};
} // namespace QodeAssist::Chat

View File

@ -27,8 +27,26 @@ Rectangle {
property alias msgModel: msgCreator.model
property alias messageAttachments: attachmentsModel.model
property string textFontFamily: Qt.application.font.family
property string codeFontFamily: {
switch (Qt.platform.os) {
case "windows":
return "Consolas";
case "osx":
return "Menlo";
case "linux":
return "DejaVu Sans Mono";
default:
return "monospace";
}
}
property int textFontSize: Qt.application.font.pointSize
property int codeFontSize: Qt.application.font.pointSize
property int textFormat: 0
property bool isUserMessage: false
property int messageIndex: -1
property real listViewContentY: 0
signal resetChatToMessage(int index)
@ -84,6 +102,8 @@ Rectangle {
id: codeBlockComponent
CodeBlockComponent {
itemData: msgCreatorDelegate.modelData
blockStart: root.y + msgCreatorDelegate.y
currentContentY: root.listViewContentY
}
}
}
@ -140,8 +160,7 @@ Rectangle {
anchors {
right: parent.right
bottom: parent.bottom
bottomMargin: 2
top: parent.top
}
text: qsTr("ResetTo")
@ -156,11 +175,28 @@ Rectangle {
height: implicitHeight + 10
verticalAlignment: Text.AlignVCenter
leftPadding: 10
text: itemData.text
text: textFormat == Text.MarkdownText ? utils.getSafeMarkdownText(itemData.text)
: itemData.text
font.family: root.textFontFamily
font.pointSize: root.textFontSize
textFormat: {
if (root.textFormat == 0) {
return Text.MarkdownText
} else if (root.textFormat == 1) {
return Text.RichText
} else {
return Text.PlainText
}
}
ChatUtils {
id: utils
}
}
component CodeBlockComponent : CodeBlock {
id: codeblock
required property var itemData
anchors {
left: parent.left
@ -171,5 +207,7 @@ Rectangle {
code: itemData.text
language: itemData.language
codeFontFamily: root.codeFontFamily
codeFontSize: root.codeFontSize
}
}

View File

@ -99,6 +99,12 @@ ChatRootView {
messageAttachments: model.attachments
isUserMessage: model.roleType === ChatModel.User
messageIndex: index
listViewContentY: chatListView.contentY
textFontFamily: root.textFontFamily
codeFontFamily: root.codeFontFamily
codeFontSize: root.codeFontSize
textFontSize: root.textFontSize
textFormat: root.textFormat
onResetChatToMessage: function(index) {
messageInput.text = model.content

View File

@ -27,17 +27,25 @@ Rectangle {
property string code: ""
property string language: ""
readonly property string monospaceFont: {
switch (Qt.platform.os) {
case "windows":
return "Consolas";
case "osx":
return "Menlo";
case "linux":
return "DejaVu Sans Mono";
default:
return "monospace";
property real currentContentY: 0
property real blockStart: 0
property alias codeFontFamily: codeText.font.family
property alias codeFontSize: codeText.font.pointSize
readonly property real buttonTopMargin: 5
readonly property real blockEnd: blockStart + root.height
readonly property real maxButtonOffset: Math.max(0, root.height - copyButton.height - buttonTopMargin)
readonly property real buttonPosition: {
if (currentContentY > blockEnd) {
return buttonTopMargin;
}
else if (currentContentY > blockStart) {
let offset = currentContentY - blockStart;
return Math.min(offset, maxButtonOffset);
}
return buttonTopMargin;
}
color: palette.alternateBase
@ -45,7 +53,6 @@ Rectangle {
: Qt.lighter(root.color, 1.3)
border.width: 2
radius: 4
implicitWidth: parent.width
implicitHeight: codeText.implicitHeight + 20
@ -55,14 +62,11 @@ Rectangle {
TextEdit {
id: codeText
anchors.fill: parent
anchors.margins: 10
text: root.code
readOnly: true
selectByMouse: true
font.family: root.monospaceFont
font.pointSize: Qt.application.font.pointSize
color: parent.color.hslLightness > 0.5 ? "black" : "white"
wrapMode: Text.WordWrap
selectionColor: palette.highlight
@ -77,14 +81,20 @@ Rectangle {
text: root.language
color: root.color.hslLightness > 0.5 ? Qt.darker(root.color, 1.1)
: Qt.lighter(root.color, 1.1)
font.pointSize: 8
font.pointSize: codeText.font.pointSize - 4
}
QoAButton {
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: 5
text: "Copy"
id: copyButton
anchors {
top: parent.top
topMargin: root.buttonPosition
right: parent.right
rightMargin: root.buttonTopMargin
}
text: qsTr("Copy")
onClicked: {
utils.copyToClipboard(root.code)
text = qsTr("Copied")

View File

@ -25,7 +25,6 @@ TextEdit {
readOnly: true
selectByMouse: true
wrapMode: Text.WordWrap
textFormat: Text.StyledText
selectionColor: palette.highlight
color: palette.text
}

View File

@ -57,6 +57,17 @@ LLMClientInterface::LLMClientInterface(
&LLMCore::RequestHandler::completionReceived,
this,
&LLMClientInterface::sendCompletionToClient);
// TODO handle error
// connect(
// &m_requestHandler,
// &LLMCore::RequestHandler::requestFinished,
// this,
// [this](const QString &, bool success, const QString &errorString) {
// if (!success) {
// emit error(errorString);
// }
// });
}
Utils::FilePath LLMClientInterface::serverDeviceTemplate() const
@ -207,10 +218,8 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
: QString{"generateContent?"};
config.url = QUrl(QString("%1/models/%2:%3").arg(url, modelName, stream));
} else {
config.url = QUrl(QString("%1%2").arg(
url,
promptTemplate->type() == LLMCore::TemplateType::FIM ? provider->completionEndpoint()
: provider->chatEndpoint()));
config.url = QUrl(
QString("%1%2").arg(url, endpoint(provider, promptTemplate->type(), isPreset1Active)));
config.providerRequest = {{"model", modelName}, {"stream", m_completeSettings.stream()}};
}
config.apiKey = provider->apiKey();
@ -289,6 +298,26 @@ LLMCore::ContextData LLMClientInterface::prepareContext(
return reader.prepareContext(lineNumber, cursorPosition, m_completeSettings);
}
QString LLMClientInterface::endpoint(
LLMCore::Provider *provider, LLMCore::TemplateType type, bool isLanguageSpecify)
{
QString endpoint;
auto endpointMode = isLanguageSpecify ? m_generalSettings.ccPreset1EndpointMode.stringValue()
: m_generalSettings.ccEndpointMode.stringValue();
if (endpointMode == "Auto") {
endpoint = type == LLMCore::TemplateType::FIM ? provider->completionEndpoint()
: provider->chatEndpoint();
} else if (endpointMode == "Custom") {
endpoint = isLanguageSpecify ? m_generalSettings.ccPreset1CustomEndpoint()
: m_generalSettings.ccCustomEndpoint();
} else if (endpointMode == "FIM") {
endpoint = provider->completionEndpoint();
} else if (endpointMode == "Chat") {
endpoint = provider->chatEndpoint();
}
return endpoint;
}
Context::ContextManager *LLMClientInterface::contextManager() const
{
return m_contextManager;
@ -327,7 +356,9 @@ void LLMClientInterface::sendCompletionToClient(
completionItem[LanguageServerProtocol::textKey] = processedCompletion;
QJsonObject range;
range["start"] = position;
range["end"] = position;
QJsonObject end = position;
end["character"] = position["character"].toInt() + processedCompletion.length();
range["end"] = end;
completionItem[LanguageServerProtocol::rangeKey] = range;
completionItem[LanguageServerProtocol::positionKey] = position;
completions.append(completionItem);

View File

@ -77,6 +77,7 @@ private:
LLMCore::ContextData prepareContext(
const QJsonObject &request, const Context::DocumentInfo &documentInfo);
QString endpoint(LLMCore::Provider *provider, LLMCore::TemplateType type, bool isLanguageSpecify);
const Settings::CodeCompletionSettings &m_completeSettings;
const Settings::GeneralSettings &m_generalSettings;

View File

@ -29,6 +29,36 @@
namespace QodeAssist {
QString mergeWithRightText(const QString &suggestion, const QString &rightText)
{
if (suggestion.isEmpty() || rightText.isEmpty()) {
return suggestion;
}
int j = 0;
QString processed = rightText;
QSet<int> matchedPositions;
for (int i = 0; i < suggestion.length() && j < processed.length(); ++i) {
if (suggestion[i] == processed[j]) {
matchedPositions.insert(j);
++j;
}
}
if (matchedPositions.isEmpty()) {
return suggestion + rightText;
}
QList<int> positions = matchedPositions.values();
std::sort(positions.begin(), positions.end(), std::greater<int>());
for (int pos : positions) {
processed.remove(pos, 1);
}
return suggestion;
}
LLMSuggestion::LLMSuggestion(
const QList<Data> &suggestions, QTextDocument *sourceDocument, int currentCompletion)
: TextEditor::CyclicSuggestion(suggestions, sourceDocument, currentCompletion)
@ -43,16 +73,23 @@ LLMSuggestion::LLMSuggestion(
QTextCursor cursor(sourceDocument);
cursor.setPosition(startPos);
cursor.setPosition(endPos, QTextCursor::KeepAnchor);
QTextBlock block = cursor.block();
QString blockText = block.text();
int startPosInBlock = startPos - block.position();
int endPosInBlock = endPos - block.position();
int cursorPositionInBlock = cursor.positionInBlock();
blockText.replace(startPosInBlock, endPosInBlock - startPosInBlock, data.text);
replacementDocument()->setPlainText(blockText);
QString rightText = blockText.mid(cursorPositionInBlock);
if (!data.text.contains('\n')) {
QString processedRightText = mergeWithRightText(data.text, rightText);
processedRightText = processedRightText.mid(data.text.length());
QString displayText = blockText.left(cursorPositionInBlock) + data.text
+ processedRightText;
replacementDocument()->setPlainText(displayText);
} else {
QString displayText = blockText.left(cursorPositionInBlock) + data.text;
replacementDocument()->setPlainText(displayText);
}
}
bool LLMSuggestion::applyWord(TextEditor::TextEditorWidget *widget)
@ -77,31 +114,82 @@ bool LLMSuggestion::applyPart(Part part, TextEditor::TextEditorWidget *widget)
int next = part == Word ? Utils::endOfNextWord(text, startPos) : text.indexOf('\n', startPos);
if (next == -1)
return apply();
if (next == -1) {
if (part == Line) {
next = text.length();
} else {
return apply();
}
}
if (part == Line)
++next;
QString subText = text.mid(startPos, next - startPos);
if (subText.isEmpty())
if (subText.isEmpty()) {
return false;
}
currentCursor.insertText(subText);
QTextBlock currentBlock = currentCursor.block();
QString textAfterCursor = currentBlock.text().mid(currentCursor.positionInBlock());
if (const int seperatorPos = subText.lastIndexOf('\n'); seperatorPos >= 0) {
const QString newCompletionText = text.mid(startPos + seperatorPos + 1);
if (!newCompletionText.isEmpty()) {
const Utils::Text::Position newStart{int(range.begin.line + subText.count('\n')), 0};
const Utils::Text::Position
newEnd{newStart.line, int(subText.length() - seperatorPos - 1)};
const Utils::Text::Range newRange{newStart, newEnd};
const QList<Data> newSuggestion{{newRange, newEnd, newCompletionText}};
widget->insertSuggestion(
std::make_unique<LLMSuggestion>(newSuggestion, widget->document(), 0));
if (!subText.contains('\n')) {
QTextCursor deleteCursor = currentCursor;
deleteCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
deleteCursor.removeSelectedText();
QString mergedText = mergeWithRightText(subText, textAfterCursor);
currentCursor.insertText(mergedText);
} else {
currentCursor.insertText(subText);
if (const int seperatorPos = subText.lastIndexOf('\n'); seperatorPos >= 0) {
const QString newCompletionText = text.mid(startPos + seperatorPos + 1);
if (!newCompletionText.isEmpty()) {
const Utils::Text::Position newStart{int(range.begin.line + subText.count('\n')), 0};
const Utils::Text::Position newEnd{newStart.line, int(newCompletionText.length())};
const Utils::Text::Range newRange{newStart, newEnd};
const QList<Data> newSuggestion{{newRange, newEnd, newCompletionText}};
widget->insertSuggestion(
std::make_unique<LLMSuggestion>(newSuggestion, widget->document(), 0));
}
}
}
return false;
}
bool LLMSuggestion::apply()
{
const Utils::Text::Range range = suggestions()[currentSuggestion()].range;
const QTextCursor cursor = range.begin.toTextCursor(sourceDocument());
const QString text = suggestions()[currentSuggestion()].text;
QTextBlock currentBlock = cursor.block();
QString textAfterCursor = currentBlock.text().mid(cursor.positionInBlock());
QTextCursor editCursor = cursor;
int firstLineEnd = text.indexOf('\n');
if (firstLineEnd != -1) {
QString firstLine = text.left(firstLineEnd);
QString restOfText = text.mid(firstLineEnd);
editCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
editCursor.removeSelectedText();
QString mergedFirstLine = mergeWithRightText(firstLine, textAfterCursor);
editCursor.insertText(mergedFirstLine + restOfText);
} else {
editCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
editCursor.removeSelectedText();
QString mergedText = mergeWithRightText(text, textAfterCursor);
editCursor.insertText(mergedText);
}
return true;
}
} // namespace QodeAssist

View File

@ -40,5 +40,6 @@ public:
bool applyWord(TextEditor::TextEditorWidget *widget) override;
bool applyLine(TextEditor::TextEditorWidget *widget) override;
bool applyPart(Part part, TextEditor::TextEditorWidget *widget);
bool apply() override;
};
} // namespace QodeAssist

View File

@ -1,7 +1,7 @@
{
"Id" : "qodeassist",
"Name" : "QodeAssist",
"Version" : "0.5.9",
"Version" : "0.5.13",
"Vendor" : "Petr Mironychev",
"VendorId" : "petrmironychev",
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",

View File

@ -3,6 +3,7 @@
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/Palm1r/QodeAssist/total?color=41%2C173%2C71)
![GitHub Tag](https://img.shields.io/github/v/tag/Palm1r/QodeAssist)
![Static Badge](https://img.shields.io/badge/QtCreator-16.0.1-brightgreen)
![Static Badge](https://img.shields.io/badge/QtCreator-16.0.2-brightgreen)
[![](https://dcbadge.limes.pink/api/server/BGMkUsXUgf?style=flat)](https://discord.gg/BGMkUsXUgf)
![qodeassist-icon](https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d) QodeAssist is an AI-powered coding assistant plugin for Qt Creator. It provides intelligent code completion and suggestions for C++ and QML, leveraging large language models through local providers like Ollama. Enhance your coding productivity with context-aware AI assistance directly in your Qt development environment.
@ -23,10 +24,12 @@
7. [Configure for Ollama](#configure-for-ollama)
8. [Configure for llama.cpp](#configure-for-llamacpp)
9. [System Prompt Configuration](#system-prompt-configuration)
10. [File Context Features](#file-context-features)
11. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
12. [Development Progress](#development-progress)
13. [Hotkeys](#hotkeys)
10. [File Context Feature](#file-context-feature)
11. [Quick Refactoring Feature](#quick-refactoring-feature)
12. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
13. [Development Progress](#development-progress)
14. [Hotkeys](#hotkeys)
15. [Ignoring Files](#ignoring-files)
14. [Troubleshooting](#troubleshooting)
15. [Support the Development](#support-the-development-of-qodeassist)
16. [How to Build](#how-to-build)
@ -201,7 +204,7 @@ You're all set! QodeAssist is now ready to use in Qt Creator.
The plugin comes with default system prompts optimized for chat and instruct models, as these currently provide better results for code assistance. If you prefer using FIM (Fill-in-Middle) models, you can easily customize the system prompt in the settings.
## File Context Features
## File Context Feature
QodeAssist provides two powerful ways to include source code files in your chat conversations: Attachments and Linked Files. Each serves a distinct purpose and helps provide better context for the AI assistant.
@ -234,8 +237,21 @@ Linked files provide persistent context throughout the conversation:
- Supports automatic syncing with open editor files (can be enabled in settings)
- Files can be added/removed at any time during the conversation
## Quick Refactoring Feature
### Setup
Since this is actually a small chat with redirected output, the main settings of the provider, model and template are taken from the chat settings
### Using
The request to model consist of instructions to model, selection code and cursor position
The default instruction is: "Refactor the code to improve its quality and maintainability." and sending if text field is empty
Also there buttons to quick call instractions:
* Repeat latest instruction, will activate after sending first request in QtCreator session
* Improve current selection code
* Suggestion alternative variant of selection code
* Other instructions[TBD]
## QtCreator Version Compatibility
- QtCreator 16.0.2 - 0.5.13 - 0.x.x
- QtCreator 16.0.1 - 0.5.7 - 0.x.x
- QtCreator 16.0.0 - 0.5.2 - 0.5.6
- QtCreator 15.0.1 - 0.4.8 - 0.5.1
@ -251,6 +267,7 @@ Linked files provide persistent context throughout the conversation:
- [x] Sharing diff with model
- [ ] Sharing project source with model
- [ ] Support for more providers and models
- [ ] Support MCP
## Hotkeys
@ -264,6 +281,30 @@ Linked files provide persistent context throughout the conversation:
- on Mac: Option + Command + R
- on Windows: Ctrl + Alt + R
- on Linux with KDE Plasma: Ctrl + Alt + R
## Ignoring Files
QodeAssist supports the ability to ignore files in context using a .qodeassistignore file. This allows you to exclude specific files from the context during code completion and in the chat assistant, which is especially useful for large projects.
### How to Use .qodeassistignore
- Create a .qodeassistignore file in the root directory of your project near CMakeLists.txt or pro.
- Add patterns for files and directories that should be excluded from the context.
- QodeAssist will automatically detect this file and apply the exclusion rules.
### .qodeassistignore File Format
The file format is similar to .gitignore:
- Each pattern is written on a separate line
- Empty lines are ignored
- Lines starting with # are considered comments
- Standard wildcards work the same as in .gitignore
- To negate a pattern, use ! at the beginning of the line
```
# Ignore all files in the build directory
/build
*.tmp
# Ignore a specific file
src/generated/autogen.cpp
```
## Troubleshooting

View File

@ -22,14 +22,51 @@
#include <QJsonDocument>
#include <QNetworkReply>
#include <QThread>
namespace QodeAssist::LLMCore {
RequestHandler::RequestHandler(QObject *parent)
: RequestHandlerBase(parent)
{}
, m_manager(new QNetworkAccessManager(this))
{
connect(
this,
&RequestHandler::doSendRequest,
this,
&RequestHandler::sendLLMRequestInternal,
Qt::QueuedConnection);
connect(
this,
&RequestHandler::doCancelRequest,
this,
&RequestHandler::cancelRequestInternal,
Qt::QueuedConnection);
}
RequestHandler::~RequestHandler()
{
for (auto reply : m_activeRequests) {
reply->abort();
reply->deleteLater();
}
m_activeRequests.clear();
m_accumulatedResponses.clear();
}
void RequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &request)
{
emit doSendRequest(config, request);
}
bool RequestHandler::cancelRequest(const QString &id)
{
emit doCancelRequest(id);
return true;
}
void RequestHandler::sendLLMRequestInternal(const LLMConfig &config, const QJsonObject &request)
{
LOG_MESSAGE(QString("Sending request to llm: \nurl: %1\nRequest body:\n%2")
.arg(
@ -37,12 +74,13 @@ void RequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &
QString::fromUtf8(
QJsonDocument(config.providerRequest).toJson(QJsonDocument::Indented))));
QNetworkAccessManager *manager = new QNetworkAccessManager();
QNetworkRequest networkRequest(config.url);
networkRequest.setTransferTimeout(300000);
config.provider->prepareNetworkRequest(networkRequest);
QNetworkReply *reply
= manager->post(networkRequest, QJsonDocument(config.providerRequest).toJson());
= m_manager->post(networkRequest, QJsonDocument(config.providerRequest).toJson());
if (!reply) {
LOG_MESSAGE("Error: Failed to create network reply");
return;
@ -55,24 +93,28 @@ void RequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &
handleLLMResponse(reply, request, config);
});
connect(reply, &QNetworkReply::finished, this, [this, reply, requestId, manager]() {
m_activeRequests.remove(requestId);
if (reply->error() != QNetworkReply::NoError) {
QString errorMessage = reply->errorString();
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
connect(
reply,
&QNetworkReply::finished,
this,
[this, reply, requestId]() {
m_activeRequests.remove(requestId);
if (reply->error() != QNetworkReply::NoError) {
QString errorMessage = reply->errorString();
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
LOG_MESSAGE(
QString("Error details: %1\nStatus code: %2").arg(errorMessage).arg(statusCode));
LOG_MESSAGE(
QString("Error details: %1\nStatus code: %2").arg(errorMessage).arg(statusCode));
emit requestFinished(requestId, false, errorMessage);
} else {
LOG_MESSAGE("Request finished successfully");
emit requestFinished(requestId, true, QString());
}
emit requestFinished(requestId, false, errorMessage);
} else {
LOG_MESSAGE("Request finished successfully");
emit requestFinished(requestId, true, QString());
}
reply->deleteLater();
manager->deleteLater();
});
reply->deleteLater();
},
Qt::QueuedConnection);
}
void RequestHandler::handleLLMResponse(
@ -102,17 +144,18 @@ void RequestHandler::handleLLMResponse(
m_accumulatedResponses.remove(reply);
}
bool RequestHandler::cancelRequest(const QString &id)
void RequestHandler::cancelRequestInternal(const QString &id)
{
QMutexLocker locker(&m_mutex);
if (m_activeRequests.contains(id)) {
QNetworkReply *reply = m_activeRequests[id];
reply->abort();
m_activeRequests.remove(id);
m_accumulatedResponses.remove(reply);
locker.unlock();
emit requestCancelled(id);
return true;
}
return false;
}
bool RequestHandler::processSingleLineCompletion(

View File

@ -20,6 +20,7 @@
#pragma once
#include <QJsonObject>
#include <QMutex>
#include <QNetworkAccessManager>
#include <QObject>
@ -32,16 +33,32 @@ namespace QodeAssist::LLMCore {
class RequestHandler : public RequestHandlerBase
{
Q_OBJECT
public:
explicit RequestHandler(QObject *parent = nullptr);
~RequestHandler() override;
void sendLLMRequest(const LLMConfig &config, const QJsonObject &request) override;
bool cancelRequest(const QString &id) override;
void handleLLMResponse(QNetworkReply *reply, const QJsonObject &request, const LLMConfig &config);
signals:
void doSendRequest(QodeAssist::LLMCore::LLMConfig config, QJsonObject request);
void doCancelRequest(QString id);
private slots:
void sendLLMRequestInternal(
const QodeAssist::LLMCore::LLMConfig &config, const QJsonObject &request);
void cancelRequestInternal(const QString &id);
void handleLLMResponse(
QNetworkReply *reply,
const QJsonObject &request,
const QodeAssist::LLMCore::LLMConfig &config);
private:
QMap<QString, QNetworkReply *> m_activeRequests;
QMap<QNetworkReply *, QString> m_accumulatedResponses;
QNetworkAccessManager *m_manager;
QMutex m_mutex;
bool processSingleLineCompletion(
QNetworkReply *reply,

View File

@ -22,6 +22,8 @@
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include <utils/layoutbuilder.h>
#include <QApplication>
#include <QFontDatabase>
#include <QMessageBox>
#include "SettingsConstants.hpp"
@ -47,8 +49,8 @@ ChatAssistantSettings::ChatAssistantSettings()
chatTokensThreshold.setLabelText(Tr::tr("Chat history token limit:"));
chatTokensThreshold.setToolTip(Tr::tr("Maximum number of tokens in chat history. When "
"exceeded, oldest messages will be removed."));
chatTokensThreshold.setRange(1, std::numeric_limits<qint64>::max());
chatTokensThreshold.setDefaultValue(8000);
chatTokensThreshold.setRange(1, 99999999);
chatTokensThreshold.setDefaultValue(20000);
linkOpenFiles.setSettingsKey(Constants::CA_LINK_OPEN_FILES);
linkOpenFiles.setLabelText(Tr::tr("Sync open files with assistant by default"));
@ -137,6 +139,65 @@ ChatAssistantSettings::ChatAssistantSettings()
contextWindow.setRange(-1, 10000);
contextWindow.setDefaultValue(2048);
autosave.setDefaultValue(true);
autosave.setLabelText(Tr::tr("Enable autosave when message received"));
textFontFamily.setSettingsKey(Constants::CA_TEXT_FONT_FAMILY);
textFontFamily.setLabelText(Tr::tr("Text Font:"));
textFontFamily.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
const QStringList families = QFontDatabase::families();
for (const QString &family : families) {
textFontFamily.addOption(family);
}
textFontFamily.setDefaultValue(QApplication::font().family());
textFontSize.setSettingsKey(Constants::CA_TEXT_FONT_SIZE);
textFontSize.setLabelText(Tr::tr("Text Font Size:"));
textFontSize.setDefaultValue(QApplication::font().pointSize());
codeFontFamily.setSettingsKey(Constants::CA_CODE_FONT_FAMILY);
codeFontFamily.setLabelText(Tr::tr("Code Font:"));
codeFontFamily.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
const QStringList monospaceFamilies = QFontDatabase::families(QFontDatabase::Latin);
for (const QString &family : monospaceFamilies) {
if (QFontDatabase::isFixedPitch(family)) {
codeFontFamily.addOption(family);
}
}
QString defaultMonoFont;
QStringList fixedPitchFamilies;
for (const QString &family : monospaceFamilies) {
if (QFontDatabase::isFixedPitch(family)) {
fixedPitchFamilies.append(family);
}
}
if (fixedPitchFamilies.contains("Consolas")) {
defaultMonoFont = "Consolas";
} else if (fixedPitchFamilies.contains("Courier New")) {
defaultMonoFont = "Courier New";
} else if (fixedPitchFamilies.contains("Monospace")) {
defaultMonoFont = "Monospace";
} else if (!fixedPitchFamilies.isEmpty()) {
defaultMonoFont = fixedPitchFamilies.first();
} else {
defaultMonoFont = QApplication::font().family();
}
codeFontFamily.setDefaultValue(defaultMonoFont);
codeFontSize.setSettingsKey(Constants::CA_CODE_FONT_SIZE);
codeFontSize.setLabelText(Tr::tr("Code Font Size:"));
codeFontSize.setDefaultValue(QApplication::font().pointSize());
textFormat.setSettingsKey(Constants::CA_TEXT_FORMAT);
textFormat.setLabelText(Tr::tr("Text Format:"));
textFormat.setDefaultValue(0);
textFormat.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
textFormat.addOption("Markdown");
textFormat.addOption("HTML");
textFormat.addOption("Plain Text");
resetToDefaults.m_buttonText = TrConstants::RESET_TO_DEFAULTS;
readSettings();
@ -160,6 +221,11 @@ ChatAssistantSettings::ChatAssistantSettings()
ollamaGrid.addRow({ollamaLivetime});
ollamaGrid.addRow({contextWindow});
auto chatViewSettingsGrid = Grid{};
chatViewSettingsGrid.addRow({textFontFamily, textFontSize});
chatViewSettingsGrid.addRow({codeFontFamily, codeFontSize});
chatViewSettingsGrid.addRow({textFormat});
return Column{
Row{Stretch{1}, resetToDefaults},
Space{8},
@ -181,6 +247,7 @@ ChatAssistantSettings::ChatAssistantSettings()
systemPrompt,
}},
Group{title(Tr::tr("Ollama Settings")), Column{Row{ollamaGrid, Stretch{1}}}},
Group{title(Tr::tr("Chat Settings")), Row{chatViewSettingsGrid, Stretch{1}}},
Stretch{1}};
});
}
@ -221,6 +288,11 @@ void ChatAssistantSettings::resetSettingsToDefaults()
resetAspect(ollamaLivetime);
resetAspect(contextWindow);
resetAspect(linkOpenFiles);
resetAspect(textFontFamily);
resetAspect(codeFontFamily);
resetAspect(textFontSize);
resetAspect(codeFontSize);
resetAspect(textFormat);
}
}

View File

@ -63,6 +63,13 @@ public:
Utils::StringAspect ollamaLivetime{this};
Utils::IntegerAspect contextWindow{this};
// Visuals settings
Utils::SelectionAspect textFontFamily{this};
Utils::IntegerAspect textFontSize{this};
Utils::SelectionAspect codeFontFamily{this};
Utils::IntegerAspect codeFontSize{this};
Utils::SelectionAspect textFormat{this};
private:
void setupConnections();
void resetSettingsToDefaults();

View File

@ -99,9 +99,20 @@ GeneralSettings::GeneralSettings()
ccSelectTemplate.m_buttonText = TrConstants::SELECT;
initStringAspect(ccUrl, Constants::CC_URL, TrConstants::URL, "http://localhost:11434");
ccUrl.setHistoryCompleter(Constants::CC_URL_HISTORY);
ccUrl.setHistoryCompleter(Constants::CC_CUSTOM_ENDPOINT_HISTORY);
ccSetUrl.m_buttonText = TrConstants::SELECT;
ccEndpointMode.setSettingsKey(Constants::CC_ENDPOINT_MODE);
ccEndpointMode.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
ccEndpointMode.addOption("Auto");
ccEndpointMode.addOption("Custom");
ccEndpointMode.addOption("FIM");
ccEndpointMode.addOption("Chat");
ccEndpointMode.setDefaultValue("Auto");
initStringAspect(ccCustomEndpoint, Constants::CC_CUSTOM_ENDPOINT, TrConstants::ENDPOINT_MODE, "");
ccCustomEndpoint.setHistoryCompleter(Constants::CC_CUSTOM_ENDPOINT_HISTORY);
ccStatus.setDisplayStyle(Utils::StringAspect::LabelDisplay);
ccStatus.setLabelText(TrConstants::STATUS);
ccStatus.setDefaultValue("");
@ -134,6 +145,21 @@ GeneralSettings::GeneralSettings()
ccPreset1Url.setHistoryCompleter(Constants::CC_PRESET1_URL_HISTORY);
ccPreset1SetUrl.m_buttonText = TrConstants::SELECT;
ccPreset1EndpointMode.setSettingsKey(Constants::CC_PRESET1_ENDPOINT_MODE);
ccPreset1EndpointMode.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
ccPreset1EndpointMode.addOption("Auto");
ccPreset1EndpointMode.addOption("Custom");
ccPreset1EndpointMode.addOption("FIM");
ccPreset1EndpointMode.addOption("Chat");
ccPreset1EndpointMode.setDefaultValue("Auto");
initStringAspect(
ccPreset1CustomEndpoint,
Constants::CC_PRESET1_CUSTOM_ENDPOINT,
TrConstants::ENDPOINT_MODE,
"");
ccPreset1CustomEndpoint.setHistoryCompleter(Constants::CC_PRESET1_CUSTOM_ENDPOINT_HISTORY);
initStringAspect(
ccPreset1Model, Constants::CC_PRESET1_MODEL, TrConstants::MODEL, "qwen2.5-coder:7b");
ccPreset1Model.setHistoryCompleter(Constants::CC_PRESET1_MODEL_HISTORY);
@ -162,6 +188,17 @@ GeneralSettings::GeneralSettings()
caUrl.setHistoryCompleter(Constants::CA_URL_HISTORY);
caSetUrl.m_buttonText = TrConstants::SELECT;
caEndpointMode.setSettingsKey(Constants::CA_ENDPOINT_MODE);
caEndpointMode.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
caEndpointMode.addOption("Auto");
caEndpointMode.addOption("Custom");
caEndpointMode.addOption("FIM");
caEndpointMode.addOption("Chat");
caEndpointMode.setDefaultValue("Auto");
initStringAspect(caCustomEndpoint, Constants::CA_CUSTOM_ENDPOINT, TrConstants::ENDPOINT_MODE, "");
caCustomEndpoint.setHistoryCompleter(Constants::CA_CUSTOM_ENDPOINT_HISTORY);
caStatus.setDisplayStyle(Utils::StringAspect::LabelDisplay);
caStatus.setLabelText(TrConstants::STATUS);
caStatus.setDefaultValue("");
@ -179,6 +216,9 @@ GeneralSettings::GeneralSettings()
setupConnections();
updatePreset1Visiblity(specifyPreset1.value());
ccCustomEndpoint.setEnabled(ccEndpointMode.stringValue() == "Custom");
ccPreset1CustomEndpoint.setEnabled(ccPreset1EndpointMode.stringValue() == "Custom");
caCustomEndpoint.setEnabled(caEndpointMode.stringValue() == "Custom");
setLayouter([this]() {
using namespace Layouting;
@ -186,18 +226,21 @@ GeneralSettings::GeneralSettings()
auto ccGrid = Grid{};
ccGrid.addRow({ccProvider, ccSelectProvider});
ccGrid.addRow({ccUrl, ccSetUrl});
ccGrid.addRow({ccCustomEndpoint, ccEndpointMode});
ccGrid.addRow({ccModel, ccSelectModel});
ccGrid.addRow({ccTemplate, ccSelectTemplate});
auto ccPreset1Grid = Grid{};
ccPreset1Grid.addRow({ccPreset1Provider, ccPreset1SelectProvider});
ccPreset1Grid.addRow({ccPreset1Url, ccPreset1SetUrl});
ccPreset1Grid.addRow({ccPreset1CustomEndpoint, ccPreset1EndpointMode});
ccPreset1Grid.addRow({ccPreset1Model, ccPreset1SelectModel});
ccPreset1Grid.addRow({ccPreset1Template, ccPreset1SelectTemplate});
auto caGrid = Grid{};
caGrid.addRow({caProvider, caSelectProvider});
caGrid.addRow({caUrl, caSetUrl});
caGrid.addRow({caCustomEndpoint, caEndpointMode});
caGrid.addRow({caModel, caSelectModel});
caGrid.addRow({caTemplate, caSelectTemplate});
@ -389,6 +432,8 @@ void GeneralSettings::updatePreset1Visiblity(bool state)
ccPreset1SelectModel.updateVisibility(specifyPreset1.volatileValue());
ccPreset1Template.setVisible(specifyPreset1.volatileValue());
ccPreset1SelectTemplate.updateVisibility(specifyPreset1.volatileValue());
ccPreset1EndpointMode.setVisible(specifyPreset1.volatileValue());
ccPreset1CustomEndpoint.setVisible(specifyPreset1.volatileValue());
}
void GeneralSettings::setupConnections()
@ -404,6 +449,19 @@ void GeneralSettings::setupConnections()
connect(&specifyPreset1, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
updatePreset1Visiblity(specifyPreset1.volatileValue());
});
connect(&ccEndpointMode, &Utils::BaseAspect::volatileValueChanged, this, [this]() {
ccCustomEndpoint.setEnabled(
ccEndpointMode.volatileValue() == ccEndpointMode.indexForDisplay("Custom"));
});
connect(&ccPreset1EndpointMode, &Utils::BaseAspect::volatileValueChanged, this, [this]() {
ccPreset1CustomEndpoint.setEnabled(
ccPreset1EndpointMode.volatileValue()
== ccPreset1EndpointMode.indexForDisplay("Custom"));
});
connect(&caEndpointMode, &Utils::BaseAspect::volatileValueChanged, this, [this]() {
caCustomEndpoint.setEnabled(
caEndpointMode.volatileValue() == caEndpointMode.indexForDisplay("Custom"));
});
}
void GeneralSettings::resetPageToDefaults()
@ -433,6 +491,12 @@ void GeneralSettings::resetPageToDefaults()
resetAspect(ccPreset1Model);
resetAspect(ccPreset1Template);
resetAspect(ccPreset1Url);
resetAspect(ccEndpointMode);
resetAspect(ccCustomEndpoint);
resetAspect(ccPreset1EndpointMode);
resetAspect(ccPreset1CustomEndpoint);
resetAspect(caEndpointMode);
resetAspect(caCustomEndpoint);
writeSettings();
}
}

View File

@ -54,6 +54,9 @@ public:
Utils::StringAspect ccUrl{this};
ButtonAspect ccSetUrl{this};
Utils::SelectionAspect ccEndpointMode{this};
Utils::StringAspect ccCustomEndpoint{this};
Utils::StringAspect ccStatus{this};
ButtonAspect ccTest{this};
@ -70,6 +73,9 @@ public:
Utils::StringAspect ccPreset1Url{this};
ButtonAspect ccPreset1SetUrl{this};
Utils::SelectionAspect ccPreset1EndpointMode{this};
Utils::StringAspect ccPreset1CustomEndpoint{this};
Utils::StringAspect ccPreset1Model{this};
ButtonAspect ccPreset1SelectModel{this};
@ -89,6 +95,9 @@ public:
Utils::StringAspect caUrl{this};
ButtonAspect caSetUrl{this};
Utils::SelectionAspect caEndpointMode{this};
Utils::StringAspect caCustomEndpoint{this};
Utils::StringAspect caStatus{this};
ButtonAspect caTest{this};

View File

@ -35,6 +35,9 @@ const char CC_MODEL_HISTORY[] = "QodeAssist.ccModelHistory";
const char CC_TEMPLATE[] = "QodeAssist.ccTemplate";
const char CC_URL[] = "QodeAssist.ccUrl";
const char CC_URL_HISTORY[] = "QodeAssist.ccUrlHistory";
const char CC_ENDPOINT_MODE[] = "QodeAssist.ccEndpointMode";
const char CC_CUSTOM_ENDPOINT[] = "QodeAssist.ccCustomEndpoint";
const char CC_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.ccCustomEndpointHistory";
const char CA_PROVIDER[] = "QodeAssist.caProvider";
const char CA_MODEL[] = "QodeAssist.caModel";
@ -42,6 +45,9 @@ const char CA_MODEL_HISTORY[] = "QodeAssist.caModelHistory";
const char CA_TEMPLATE[] = "QodeAssist.caTemplate";
const char CA_URL[] = "QodeAssist.caUrl";
const char CA_URL_HISTORY[] = "QodeAssist.caUrlHistory";
const char CA_ENDPOINT_MODE[] = "QodeAssist.caEndpointMode";
const char CA_CUSTOM_ENDPOINT[] = "QodeAssist.caCustomEndpoint";
const char CA_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.caCustomEndpointHistory";
const char CC_SPECIFY_PRESET1[] = "QodeAssist.ccSpecifyPreset1";
const char CC_PRESET1_LANGUAGE[] = "QodeAssist.ccPreset1Language";
@ -51,6 +57,9 @@ const char CC_PRESET1_MODEL_HISTORY[] = "QodeAssist.ccPreset1ModelHistory";
const char CC_PRESET1_TEMPLATE[] = "QodeAssist.ccPreset1Template";
const char CC_PRESET1_URL[] = "QodeAssist.ccPreset1Url";
const char CC_PRESET1_URL_HISTORY[] = "QodeAssist.ccPreset1UrlHistory";
const char CC_PRESET1_ENDPOINT_MODE[] = "QodeAssist.caPreset1EndpointMode";
const char CC_PRESET1_CUSTOM_ENDPOINT[] = "QodeAssist.caPreset1CustomEndpointHistory";
const char CC_PRESET1_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.caPreset1CustomEndpointHistory";
// settings
const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist";
@ -151,5 +160,10 @@ const char CA_USE_FREQUENCY_PENALTY[] = "QodeAssist.chatUseFrequencyPenalty";
const char CA_FREQUENCY_PENALTY[] = "QodeAssist.chatFrequencyPenalty";
const char CA_OLLAMA_LIVETIME[] = "QodeAssist.chatOllamaLivetime";
const char CA_OLLAMA_CONTEXT_WINDOW[] = "QodeAssist.caOllamaContextWindow";
const char CA_TEXT_FONT_FAMILY[] = "QodeAssist.caTextFontFamily";
const char CA_TEXT_FONT_SIZE[] = "QodeAssist.caTextFontSize";
const char CA_CODE_FONT_FAMILY[] = "QodeAssist.caCodeFontFamily";
const char CA_CODE_FONT_SIZE[] = "QodeAssist.caCodeFontSize";
const char CA_TEXT_FORMAT[] = "QodeAssist.caTextFormat";
} // namespace QodeAssist::Constants

View File

@ -44,6 +44,7 @@ inline const char *ENABLE_CHECK_UPDATE_ON_START
inline const char *ENABLE_CHAT = QT_TRANSLATE_NOOP(
"QtC::QodeAssist",
"Enable Chat(If you have performance issues try disabling this, need restart QtC)");
inline const char *ENDPOINT_MODE = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Endpoint Mode:");
inline const char *CODE_COMPLETION = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Code Completion");
inline const char *CHAT_ASSISTANT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Chat Assistant");

View File

@ -38,7 +38,6 @@ namespace QodeAssist {
void CompletionProgressHandler::showProgress(TextEditor::TextEditorWidget *widget)
{
m_widget = widget;
m_isActive = true;
if (m_widget) {
const QRect cursorRect = m_widget->cursorRect(m_widget->textCursor());
@ -54,14 +53,13 @@ void CompletionProgressHandler::showProgress(TextEditor::TextEditorWidget *widge
void CompletionProgressHandler::hideProgress()
{
m_isActive = false;
Utils::ToolTip::hide();
Utils::ToolTip::hideImmediately();
}
void CompletionProgressHandler::identifyMatch(
TextEditor::TextEditorWidget *editorWidget, int pos, ReportPriority report)
{
if (!m_isActive || !editorWidget) {
if (!editorWidget) {
report(Priority_None);
return;
}
@ -72,7 +70,7 @@ void CompletionProgressHandler::identifyMatch(
void CompletionProgressHandler::operateTooltip(
TextEditor::TextEditorWidget *editorWidget, const QPoint &point)
{
if (!m_isActive || !editorWidget)
if (!editorWidget)
return;
auto progressWidget = new ProgressWidget(editorWidget);

View File

@ -38,7 +38,6 @@ protected:
private:
QPointer<TextEditor::TextEditorWidget> m_widget;
QPoint m_iconPosition;
bool m_isActive = false;
};
} // namespace QodeAssist