Compare commits

...

16 Commits

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
27 changed files with 523 additions and 334 deletions

View File

@ -70,8 +70,13 @@
}, },
{ {
"version": "0.5.11", "version": "0.5.11",
"is_latest": true, "is_latest": false,
"released_at": "2025-04-24T21:00:00Z" "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", "icon": "https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d",

View File

@ -12,16 +12,13 @@ on:
env: env:
PLUGIN_NAME: QodeAssist 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" MACOS_DEPLOYMENT_TARGET: "11.0"
CMAKE_VERSION: "3.29.6" CMAKE_VERSION: "3.29.6"
NINJA_VERSION: "1.12.1" NINJA_VERSION: "1.12.1"
jobs: jobs:
build: 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 }} runs-on: ${{ matrix.config.os }}
outputs: outputs:
tag: ${{ steps.git.outputs.tag }} tag: ${{ steps.git.outputs.tag }}
@ -47,12 +44,18 @@ jobs:
platform: mac_x64, platform: mac_x64,
cc: "clang", cxx: "clang++" 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: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Checkout submodules - name: Checkout submodules
id: git id: git
@ -61,7 +64,12 @@ jobs:
if (${{github.ref}} MATCHES "tags/v(.*)") if (${{github.ref}} MATCHES "tags/v(.*)")
file(APPEND "$ENV{GITHUB_OUTPUT}" "tag=${CMAKE_MATCH_1}") file(APPEND "$ENV{GITHUB_OUTPUT}" "tag=${CMAKE_MATCH_1}")
else() 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() endif()
- name: Download Ninja and CMake - name: Download Ninja and CMake
@ -96,7 +104,7 @@ jobs:
id: qt id: qt
shell: cmake -P {0} shell: cmake -P {0}
run: | run: |
set(qt_version "$ENV{QT_VERSION}") set(qt_version "${{ matrix.qt_config.qt_version }}")
string(REPLACE "." "" qt_version_dotless "${qt_version}") string(REPLACE "." "" qt_version_dotless "${qt_version}")
if ("${{ runner.os }}" STREQUAL "Windows") if ("${{ runner.os }}" STREQUAL "Windows")
@ -174,10 +182,11 @@ jobs:
endif() endif()
- name: Download Qt Creator - name: Download Qt Creator
uses: qt-creator/install-dev-package@v1.2 uses: qt-creator/install-dev-package@v2.0
with: with:
version: ${{ env.QT_CREATOR_VERSION }} version: ${{ matrix.qt_config.qt_creator_version }}
unzip-to: 'qtcreator' unzip-to: 'qtcreator'
platform: ${{ matrix.config.platform }}
- name: Extract Qt Creator - name: Extract Qt Creator
id: qt_creator id: qt_creator
@ -223,7 +232,7 @@ jobs:
COMMAND python COMMAND python
-u -u
"${{ steps.qt_creator.outputs.qtc_dir }}/${build_plugin_py}" "${{ 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 . --src .
--build build --build build
--qt-path "${{ steps.qt.outputs.qt_dir }}" --qt-path "${{ steps.qt.outputs.qt_dir }}"
@ -241,68 +250,18 @@ jobs:
- name: Upload - name: Upload
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
path: ./${{ env.PLUGIN_NAME }}-v${{ steps.git.outputs.tag }}-QtC${{ env.QT_CREATOR_VERSION }}-${{ matrix.config.artifact }}.7z 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${{ env.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
# 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
- name: Run unit tests - name: Run unit tests
if: startsWith(matrix.config.os, 'ubuntu') if: startsWith(matrix.config.os, 'ubuntu')
run: | run: |
xvfb-run ./build/build/test/QodeAssistTest 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: release:
if: contains(github.ref, 'tags/v') if: contains(github.ref, 'tags/v')
runs-on: ubuntu-latest runs-on: ubuntu-22.04
needs: [build, update_json] needs: [build]
steps: steps:
- name: Download artifacts - name: Download artifacts

View File

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

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(); updateInputTokensCount();
} }
@ -552,4 +577,29 @@ bool ChatRootView::shouldIgnoreFileForAttach(const Utils::FilePath &filePath)
return false; 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 } // namespace QodeAssist::Chat

View File

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

View File

@ -29,4 +29,40 @@ void ChatUtils::copyToClipboard(const QString &text)
QGuiApplication::clipboard()->setText(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 } // namespace QodeAssist::Chat

View File

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

View File

@ -27,8 +27,26 @@ Rectangle {
property alias msgModel: msgCreator.model property alias msgModel: msgCreator.model
property alias messageAttachments: attachmentsModel.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 bool isUserMessage: false
property int messageIndex: -1 property int messageIndex: -1
property real listViewContentY: 0
signal resetChatToMessage(int index) signal resetChatToMessage(int index)
@ -84,6 +102,8 @@ Rectangle {
id: codeBlockComponent id: codeBlockComponent
CodeBlockComponent { CodeBlockComponent {
itemData: msgCreatorDelegate.modelData itemData: msgCreatorDelegate.modelData
blockStart: root.y + msgCreatorDelegate.y
currentContentY: root.listViewContentY
} }
} }
} }
@ -155,11 +175,28 @@ Rectangle {
height: implicitHeight + 10 height: implicitHeight + 10
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
leftPadding: 10 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 { component CodeBlockComponent : CodeBlock {
id: codeblock
required property var itemData required property var itemData
anchors { anchors {
left: parent.left left: parent.left
@ -170,5 +207,7 @@ Rectangle {
code: itemData.text code: itemData.text
language: itemData.language language: itemData.language
codeFontFamily: root.codeFontFamily
codeFontSize: root.codeFontSize
} }
} }

View File

@ -76,10 +76,6 @@ ChatRootView {
text: qsTr("Latest chat file name: %1").arg(root.chatFileName.length > 0 ? root.chatFileName : "Unsaved") text: qsTr("Latest chat file name: %1").arg(root.chatFileName.length > 0 ? root.chatFileName : "Unsaved")
} }
openChatHistory.onClicked: root.openChatHistoryFolder() openChatHistory.onClicked: root.openChatHistoryFolder()
expandScrollbar {
text: scroll.isPreviewMode ? "»" : "«"
onClicked: scroll.isPreviewMode = !scroll.isPreviewMode
}
} }
ListView { ListView {
@ -103,6 +99,12 @@ ChatRootView {
messageAttachments: model.attachments messageAttachments: model.attachments
isUserMessage: model.roleType === ChatModel.User isUserMessage: model.roleType === ChatModel.User
messageIndex: index messageIndex: index
listViewContentY: chatListView.contentY
textFontFamily: root.textFontFamily
codeFontFamily: root.codeFontFamily
codeFontSize: root.codeFontSize
textFontSize: root.textFontSize
textFormat: root.textFormat
onResetChatToMessage: function(index) { onResetChatToMessage: function(index) {
messageInput.text = model.content messageInput.text = model.content
@ -118,50 +120,6 @@ ChatRootView {
ScrollBar.vertical: QQC.ScrollBar { ScrollBar.vertical: QQC.ScrollBar {
id: scroll id: scroll
property bool isPreviewMode: false
readonly property int previewWidth: 30
implicitWidth: isPreviewMode ? scroll.previewWidth : 16
contentItem: Rectangle {
implicitWidth: scroll.isPreviewMode ? scroll.previewWidth : 6
implicitHeight: 100
radius: 3
color: scroll.pressed ? palette.dark :
scroll.hovered ? palette.mid :
palette.button
Behavior on implicitWidth {
NumberAnimation { duration: 150 }
}
}
background: Rectangle {
color: scroll.isPreviewMode ? "transparent" :
palette.window.hslLightness > 0.5 ?
Qt.darker(palette.window, 1.1) :
Qt.lighter(palette.window, 1.1)
radius: 3
}
ChatPreviewBar {
anchors.fill: parent
targetView: chatListView
visible: parent.isPreviewMode
opacity: parent.isPreviewMode ? 1 : 0
Behavior on opacity {
NumberAnimation { duration: 150 }
}
}
Behavior on implicitWidth {
NumberAnimation {
duration: 150
easing.type: Easing.InOutQuad
}
}
} }
onCountChanged: { onCountChanged: {

View File

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

View File

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

View File

@ -1,135 +0,0 @@
/*
* Copyright (C) 2025 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
Rectangle {
id: root
property ListView targetView: null
property int previewWidth: 50
property color userMessageColor: "#92BD6C"
property color assistantMessageColor: palette.button
width: previewWidth
color: palette.window.hslLightness > 0.5 ?
Qt.darker(palette.window, 1.1) :
Qt.lighter(palette.window, 1.1)
Behavior on opacity {
NumberAnimation {
duration: 150
easing.type: Easing.InOutQuad
}
}
Column {
id: previewContainer
anchors.fill: parent
anchors.margins: 2
spacing: 2
Repeater {
model: targetView ? targetView.model : null
Rectangle {
required property int index
required property var model
width: parent.width
height: {
if (!targetView || !targetView.count) return 0
const availableHeight = root.height - ((targetView.count - 1) * previewContainer.spacing)
return availableHeight / targetView.count
}
radius: 4
color: model.roleType === ChatModel.User ?
userMessageColor :
assistantMessageColor
opacity: root.opacity
transform: Translate {
x: root.opacity * 50 - 50
}
Behavior on transform {
NumberAnimation {
duration: 150
easing.type: Easing.InOutQuad
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if (targetView) {
targetView.positionViewAtIndex(index, ListView.Center)
}
}
HoverHandler {
id: hover
}
}
Rectangle {
anchors.fill: parent
color: palette.highlight
opacity: hover.hovered ? 0.2 : 0
radius: parent.radius
Behavior on opacity {
NumberAnimation { duration: 150 }
}
}
Rectangle {
anchors.fill: parent
color: palette.highlight
opacity: {
if (!targetView) return 0
const viewY = targetView.contentY
const viewHeight = targetView.height
const totalHeight = targetView.contentHeight
const itemPosition = index / targetView.count * totalHeight
const itemHeight = totalHeight / targetView.count
return (itemPosition + itemHeight > viewY &&
itemPosition < viewY + viewHeight) ? 0.2 : 0
}
radius: parent.radius
Behavior on opacity {
NumberAnimation { duration: 150 }
}
}
ToolTip.visible: hover.hovered
ToolTip.text: {
const maxPreviewLength = 100
return model.content.length > maxPreviewLength ?
model.content.substring(0, maxPreviewLength) + "..." :
model.content
}
}
}
}
}

View File

@ -30,7 +30,6 @@ Rectangle {
property alias tokensBadge: tokensBadgeId property alias tokensBadge: tokensBadgeId
property alias recentPath: recentPathId property alias recentPath: recentPathId
property alias openChatHistory: openChatHistoryId property alias openChatHistory: openChatHistoryId
property alias expandScrollbar: expandScrollbarId
color: palette.window.hslLightness > 0.5 ? color: palette.window.hslLightness > 0.5 ?
Qt.darker(palette.window, 1.1) : Qt.darker(palette.window, 1.1) :
@ -85,12 +84,5 @@ Rectangle {
Badge { Badge {
id: tokensBadgeId id: tokensBadgeId
} }
QoAButton {
id: expandScrollbarId
width: 16
height: 16
}
} }
} }

View File

@ -57,6 +57,17 @@ LLMClientInterface::LLMClientInterface(
&LLMCore::RequestHandler::completionReceived, &LLMCore::RequestHandler::completionReceived,
this, this,
&LLMClientInterface::sendCompletionToClient); &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 Utils::FilePath LLMClientInterface::serverDeviceTemplate() const
@ -207,10 +218,8 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
: QString{"generateContent?"}; : QString{"generateContent?"};
config.url = QUrl(QString("%1/models/%2:%3").arg(url, modelName, stream)); config.url = QUrl(QString("%1/models/%2:%3").arg(url, modelName, stream));
} else { } else {
config.url = QUrl(QString("%1%2").arg( config.url = QUrl(
url, QString("%1%2").arg(url, endpoint(provider, promptTemplate->type(), isPreset1Active)));
promptTemplate->type() == LLMCore::TemplateType::FIM ? provider->completionEndpoint()
: provider->chatEndpoint()));
config.providerRequest = {{"model", modelName}, {"stream", m_completeSettings.stream()}}; config.providerRequest = {{"model", modelName}, {"stream", m_completeSettings.stream()}};
} }
config.apiKey = provider->apiKey(); config.apiKey = provider->apiKey();
@ -289,6 +298,26 @@ LLMCore::ContextData LLMClientInterface::prepareContext(
return reader.prepareContext(lineNumber, cursorPosition, m_completeSettings); 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 Context::ContextManager *LLMClientInterface::contextManager() const
{ {
return m_contextManager; return m_contextManager;

View File

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

View File

@ -1,7 +1,7 @@
{ {
"Id" : "qodeassist", "Id" : "qodeassist",
"Name" : "QodeAssist", "Name" : "QodeAssist",
"Version" : "0.5.11", "Version" : "0.5.13",
"Vendor" : "Petr Mironychev", "Vendor" : "Petr Mironychev",
"VendorId" : "petrmironychev", "VendorId" : "petrmironychev",
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd", "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 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) ![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.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) [![](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. ![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,11 +24,12 @@
7. [Configure for Ollama](#configure-for-ollama) 7. [Configure for Ollama](#configure-for-ollama)
8. [Configure for llama.cpp](#configure-for-llamacpp) 8. [Configure for llama.cpp](#configure-for-llamacpp)
9. [System Prompt Configuration](#system-prompt-configuration) 9. [System Prompt Configuration](#system-prompt-configuration)
10. [File Context Features](#file-context-features) 10. [File Context Feature](#file-context-feature)
11. [QtCreator Version Compatibility](#qtcreator-version-compatibility) 11. [Quick Refactoring Feature](#quick-refactoring-feature)
12. [Development Progress](#development-progress) 12. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
13. [Hotkeys](#hotkeys) 13. [Development Progress](#development-progress)
14. [Ignoring Files](#ignoring-files) 14. [Hotkeys](#hotkeys)
15. [Ignoring Files](#ignoring-files)
14. [Troubleshooting](#troubleshooting) 14. [Troubleshooting](#troubleshooting)
15. [Support the Development](#support-the-development-of-qodeassist) 15. [Support the Development](#support-the-development-of-qodeassist)
16. [How to Build](#how-to-build) 16. [How to Build](#how-to-build)
@ -202,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. 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. 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.
@ -235,8 +237,21 @@ Linked files provide persistent context throughout the conversation:
- Supports automatic syncing with open editor files (can be enabled in settings) - Supports automatic syncing with open editor files (can be enabled in settings)
- Files can be added/removed at any time during the conversation - 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 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.1 - 0.5.7 - 0.x.x
- QtCreator 16.0.0 - 0.5.2 - 0.5.6 - QtCreator 16.0.0 - 0.5.2 - 0.5.6
- QtCreator 15.0.1 - 0.4.8 - 0.5.1 - QtCreator 15.0.1 - 0.4.8 - 0.5.1
@ -252,6 +267,7 @@ Linked files provide persistent context throughout the conversation:
- [x] Sharing diff with model - [x] Sharing diff with model
- [ ] Sharing project source with model - [ ] Sharing project source with model
- [ ] Support for more providers and models - [ ] Support for more providers and models
- [ ] Support MCP
## Hotkeys ## Hotkeys
@ -283,23 +299,11 @@ The file format is similar to .gitignore:
- To negate a pattern, use ! at the beginning of the line - To negate a pattern, use ! at the beginning of the line
``` ```
# Ignore all files in the build directory # Ignore all files in the build directory
build/ /build
# Ignore all temporary files
*.tmp *.tmp
*.temp
# Ignore all files with .log extension
*.log
# Ignore a specific file # Ignore a specific file
src/generated/autogen.cpp src/generated/autogen.cpp
# Ignore nested directories
**/node_modules/
# Negation - DO NOT ignore this file
!src/important.cpp
``` ```
## Troubleshooting ## Troubleshooting

View File

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

View File

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

View File

@ -22,6 +22,8 @@
#include <coreplugin/dialogs/ioptionspage.h> #include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <utils/layoutbuilder.h> #include <utils/layoutbuilder.h>
#include <QApplication>
#include <QFontDatabase>
#include <QMessageBox> #include <QMessageBox>
#include "SettingsConstants.hpp" #include "SettingsConstants.hpp"
@ -137,6 +139,65 @@ ChatAssistantSettings::ChatAssistantSettings()
contextWindow.setRange(-1, 10000); contextWindow.setRange(-1, 10000);
contextWindow.setDefaultValue(2048); 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; resetToDefaults.m_buttonText = TrConstants::RESET_TO_DEFAULTS;
readSettings(); readSettings();
@ -160,6 +221,11 @@ ChatAssistantSettings::ChatAssistantSettings()
ollamaGrid.addRow({ollamaLivetime}); ollamaGrid.addRow({ollamaLivetime});
ollamaGrid.addRow({contextWindow}); ollamaGrid.addRow({contextWindow});
auto chatViewSettingsGrid = Grid{};
chatViewSettingsGrid.addRow({textFontFamily, textFontSize});
chatViewSettingsGrid.addRow({codeFontFamily, codeFontSize});
chatViewSettingsGrid.addRow({textFormat});
return Column{ return Column{
Row{Stretch{1}, resetToDefaults}, Row{Stretch{1}, resetToDefaults},
Space{8}, Space{8},
@ -181,6 +247,7 @@ ChatAssistantSettings::ChatAssistantSettings()
systemPrompt, systemPrompt,
}}, }},
Group{title(Tr::tr("Ollama Settings")), Column{Row{ollamaGrid, Stretch{1}}}}, Group{title(Tr::tr("Ollama Settings")), Column{Row{ollamaGrid, Stretch{1}}}},
Group{title(Tr::tr("Chat Settings")), Row{chatViewSettingsGrid, Stretch{1}}},
Stretch{1}}; Stretch{1}};
}); });
} }
@ -221,6 +288,11 @@ void ChatAssistantSettings::resetSettingsToDefaults()
resetAspect(ollamaLivetime); resetAspect(ollamaLivetime);
resetAspect(contextWindow); resetAspect(contextWindow);
resetAspect(linkOpenFiles); resetAspect(linkOpenFiles);
resetAspect(textFontFamily);
resetAspect(codeFontFamily);
resetAspect(textFontSize);
resetAspect(codeFontSize);
resetAspect(textFormat);
} }
} }

View File

@ -63,6 +63,13 @@ public:
Utils::StringAspect ollamaLivetime{this}; Utils::StringAspect ollamaLivetime{this};
Utils::IntegerAspect contextWindow{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: private:
void setupConnections(); void setupConnections();
void resetSettingsToDefaults(); void resetSettingsToDefaults();

View File

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

View File

@ -54,6 +54,9 @@ public:
Utils::StringAspect ccUrl{this}; Utils::StringAspect ccUrl{this};
ButtonAspect ccSetUrl{this}; ButtonAspect ccSetUrl{this};
Utils::SelectionAspect ccEndpointMode{this};
Utils::StringAspect ccCustomEndpoint{this};
Utils::StringAspect ccStatus{this}; Utils::StringAspect ccStatus{this};
ButtonAspect ccTest{this}; ButtonAspect ccTest{this};
@ -70,6 +73,9 @@ public:
Utils::StringAspect ccPreset1Url{this}; Utils::StringAspect ccPreset1Url{this};
ButtonAspect ccPreset1SetUrl{this}; ButtonAspect ccPreset1SetUrl{this};
Utils::SelectionAspect ccPreset1EndpointMode{this};
Utils::StringAspect ccPreset1CustomEndpoint{this};
Utils::StringAspect ccPreset1Model{this}; Utils::StringAspect ccPreset1Model{this};
ButtonAspect ccPreset1SelectModel{this}; ButtonAspect ccPreset1SelectModel{this};
@ -89,6 +95,9 @@ public:
Utils::StringAspect caUrl{this}; Utils::StringAspect caUrl{this};
ButtonAspect caSetUrl{this}; ButtonAspect caSetUrl{this};
Utils::SelectionAspect caEndpointMode{this};
Utils::StringAspect caCustomEndpoint{this};
Utils::StringAspect caStatus{this}; Utils::StringAspect caStatus{this};
ButtonAspect caTest{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_TEMPLATE[] = "QodeAssist.ccTemplate";
const char CC_URL[] = "QodeAssist.ccUrl"; const char CC_URL[] = "QodeAssist.ccUrl";
const char CC_URL_HISTORY[] = "QodeAssist.ccUrlHistory"; 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_PROVIDER[] = "QodeAssist.caProvider";
const char CA_MODEL[] = "QodeAssist.caModel"; 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_TEMPLATE[] = "QodeAssist.caTemplate";
const char CA_URL[] = "QodeAssist.caUrl"; const char CA_URL[] = "QodeAssist.caUrl";
const char CA_URL_HISTORY[] = "QodeAssist.caUrlHistory"; 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_SPECIFY_PRESET1[] = "QodeAssist.ccSpecifyPreset1";
const char CC_PRESET1_LANGUAGE[] = "QodeAssist.ccPreset1Language"; 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_TEMPLATE[] = "QodeAssist.ccPreset1Template";
const char CC_PRESET1_URL[] = "QodeAssist.ccPreset1Url"; const char CC_PRESET1_URL[] = "QodeAssist.ccPreset1Url";
const char CC_PRESET1_URL_HISTORY[] = "QodeAssist.ccPreset1UrlHistory"; 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 // settings
const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist"; 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_FREQUENCY_PENALTY[] = "QodeAssist.chatFrequencyPenalty";
const char CA_OLLAMA_LIVETIME[] = "QodeAssist.chatOllamaLivetime"; const char CA_OLLAMA_LIVETIME[] = "QodeAssist.chatOllamaLivetime";
const char CA_OLLAMA_CONTEXT_WINDOW[] = "QodeAssist.caOllamaContextWindow"; 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 } // 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( inline const char *ENABLE_CHAT = QT_TRANSLATE_NOOP(
"QtC::QodeAssist", "QtC::QodeAssist",
"Enable Chat(If you have performance issues try disabling this, need restart QtC)"); "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 *CODE_COMPLETION = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Code Completion");
inline const char *CHAT_ASSISTANT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Chat Assistant"); 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) void CompletionProgressHandler::showProgress(TextEditor::TextEditorWidget *widget)
{ {
m_widget = widget; m_widget = widget;
m_isActive = true;
if (m_widget) { if (m_widget) {
const QRect cursorRect = m_widget->cursorRect(m_widget->textCursor()); const QRect cursorRect = m_widget->cursorRect(m_widget->textCursor());
@ -54,14 +53,13 @@ void CompletionProgressHandler::showProgress(TextEditor::TextEditorWidget *widge
void CompletionProgressHandler::hideProgress() void CompletionProgressHandler::hideProgress()
{ {
m_isActive = false; Utils::ToolTip::hideImmediately();
Utils::ToolTip::hide();
} }
void CompletionProgressHandler::identifyMatch( void CompletionProgressHandler::identifyMatch(
TextEditor::TextEditorWidget *editorWidget, int pos, ReportPriority report) TextEditor::TextEditorWidget *editorWidget, int pos, ReportPriority report)
{ {
if (!m_isActive || !editorWidget) { if (!editorWidget) {
report(Priority_None); report(Priority_None);
return; return;
} }
@ -72,7 +70,7 @@ void CompletionProgressHandler::identifyMatch(
void CompletionProgressHandler::operateTooltip( void CompletionProgressHandler::operateTooltip(
TextEditor::TextEditorWidget *editorWidget, const QPoint &point) TextEditor::TextEditorWidget *editorWidget, const QPoint &point)
{ {
if (!m_isActive || !editorWidget) if (!editorWidget)
return; return;
auto progressWidget = new ProgressWidget(editorWidget); auto progressWidget = new ProgressWidget(editorWidget);

View File

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