mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2025-06-04 01:28:58 -04:00
Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
af3fdb58ff | ||
|
637a4d9d4c | ||
|
7e2345773f | ||
|
14a5ddbdd8 | ||
|
e178b7daa7 | ||
|
4b353d5091 | ||
|
f7ba7b95be | ||
|
6ae95fec45 | ||
|
dad8ab2bf3 | ||
|
25a6983de0 | ||
|
4e05abc7d2 | ||
|
784529e344 | ||
|
155153a763 | ||
|
9225c0c1a9 | ||
|
43adc95857 | ||
|
ee672f2cda | ||
|
a3edb8a577 | ||
|
407d3b11c0 | ||
|
285e739074 | ||
|
f7e748ba7e | ||
|
acb1306321 | ||
|
8b38ecc29b | ||
|
cfb364f033 | ||
|
2fe6850a06 | ||
|
3e9506ca92 | ||
|
d24adff0f5 | ||
|
447324eb07 | ||
|
4ca494cc51 | ||
|
8a80dbe8f5 | ||
|
2b539bbdeb | ||
|
3f2c146df1 | ||
|
9a54f04a0d | ||
|
7a33425d1a | ||
|
711aa672f2 | ||
|
8cb6a2f6d2 | ||
|
2f9622e23e | ||
|
674b1fecde | ||
|
b36d01d2c7 | ||
|
615175bea8 | ||
|
7515599acb | ||
|
3652d4d5d9 | ||
|
75677770b2 | ||
|
329a1efd5d | ||
|
27760a3b99 |
29
.github/scripts/plugin.json
vendored
29
.github/scripts/plugin.json
vendored
@ -13,7 +13,7 @@
|
|||||||
"Linux"
|
"Linux"
|
||||||
],
|
],
|
||||||
"license": "GPLv3",
|
"license": "GPLv3",
|
||||||
"version": "0.5.7",
|
"version": "0.5.11",
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"is_pack": false,
|
"is_pack": false,
|
||||||
"released_at": null,
|
"released_at": null,
|
||||||
@ -50,8 +50,33 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "0.5.7",
|
"version": "0.5.7",
|
||||||
"is_latest": true,
|
"is_latest": false,
|
||||||
"released_at": "2025-04-14T01:00:00Z"
|
"released_at": "2025-04-14T01:00:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.5.8",
|
||||||
|
"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",
|
"icon": "https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d",
|
||||||
|
91
.github/workflows/build_cmake.yml
vendored
91
.github/workflows/build_cmake.yml
vendored
@ -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}-$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 }}-${{ 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}}-${{ 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
|
||||||
|
@ -92,6 +92,7 @@ add_qtc_plugin(QodeAssist
|
|||||||
providers/OpenRouterAIProvider.hpp providers/OpenRouterAIProvider.cpp
|
providers/OpenRouterAIProvider.hpp providers/OpenRouterAIProvider.cpp
|
||||||
providers/GoogleAIProvider.hpp providers/GoogleAIProvider.cpp
|
providers/GoogleAIProvider.hpp providers/GoogleAIProvider.cpp
|
||||||
providers/LlamaCppProvider.hpp providers/LlamaCppProvider.cpp
|
providers/LlamaCppProvider.hpp providers/LlamaCppProvider.cpp
|
||||||
|
providers/CodestralProvider.hpp providers/CodestralProvider.cpp
|
||||||
QodeAssist.qrc
|
QodeAssist.qrc
|
||||||
LSPCompletion.hpp
|
LSPCompletion.hpp
|
||||||
LLMSuggestion.hpp LLMSuggestion.cpp
|
LLMSuggestion.hpp LLMSuggestion.cpp
|
||||||
@ -105,6 +106,7 @@ add_qtc_plugin(QodeAssist
|
|||||||
widgets/ProgressWidget.hpp widgets/ProgressWidget.cpp
|
widgets/ProgressWidget.hpp widgets/ProgressWidget.cpp
|
||||||
widgets/EditorChatButton.hpp widgets/EditorChatButton.cpp
|
widgets/EditorChatButton.hpp widgets/EditorChatButton.cpp
|
||||||
widgets/EditorChatButtonHandler.hpp widgets/EditorChatButtonHandler.cpp
|
widgets/EditorChatButtonHandler.hpp widgets/EditorChatButtonHandler.cpp
|
||||||
|
widgets/QuickRefactorDialog.hpp widgets/QuickRefactorDialog.cpp
|
||||||
QuickRefactorHandler.hpp QuickRefactorHandler.cpp
|
QuickRefactorHandler.hpp QuickRefactorHandler.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,6 +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
|
||||||
|
|
||||||
RESOURCES
|
RESOURCES
|
||||||
icons/attach-file-light.svg
|
icons/attach-file-light.svg
|
||||||
icons/attach-file-dark.svg
|
icons/attach-file-dark.svg
|
||||||
|
@ -124,6 +124,7 @@ QList<MessagePart> ChatModel::processMessageContent(const QString &content) cons
|
|||||||
QRegularExpression codeBlockRegex("```(\\w*)\\n?([\\s\\S]*?)```");
|
QRegularExpression codeBlockRegex("```(\\w*)\\n?([\\s\\S]*?)```");
|
||||||
int lastIndex = 0;
|
int lastIndex = 0;
|
||||||
auto blockMatches = codeBlockRegex.globalMatch(content);
|
auto blockMatches = codeBlockRegex.globalMatch(content);
|
||||||
|
bool foundCodeBlock = blockMatches.hasNext();
|
||||||
|
|
||||||
while (blockMatches.hasNext()) {
|
while (blockMatches.hasNext()) {
|
||||||
auto match = blockMatches.next();
|
auto match = blockMatches.next();
|
||||||
@ -140,7 +141,19 @@ QList<MessagePart> ChatModel::processMessageContent(const QString &content) cons
|
|||||||
|
|
||||||
if (lastIndex < content.length()) {
|
if (lastIndex < content.length()) {
|
||||||
QString remainingText = content.mid(lastIndex).trimmed();
|
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, ""});
|
parts.append({MessagePart::Text, remainingText, ""});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,4 +210,16 @@ QString ChatModel::lastMessageId() const
|
|||||||
return !m_messages.isEmpty() ? m_messages.last().id : "";
|
return !m_messages.isEmpty() ? m_messages.last().id : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatModel::resetModelTo(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= m_messages.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (index < m_messages.size()) {
|
||||||
|
beginRemoveRows(QModelIndex(), index, m_messages.size() - 1);
|
||||||
|
m_messages.remove(index, m_messages.size() - index);
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
@ -73,6 +73,8 @@ public:
|
|||||||
QString currentModel() const;
|
QString currentModel() const;
|
||||||
QString lastMessageId() const;
|
QString lastMessageId() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE void resetModelTo(int index);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void tokensThresholdChanged();
|
void tokensThresholdChanged();
|
||||||
void modelReseted();
|
void modelReseted();
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
@ -510,7 +535,7 @@ void ChatRootView::onAppendLinkFileFromEditor(Core::IEditor *editor)
|
|||||||
{
|
{
|
||||||
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
||||||
QString filePath = document->filePath().toFSPathString();
|
QString filePath = document->filePath().toFSPathString();
|
||||||
if (!m_linkedFiles.contains(filePath)) {
|
if (!m_linkedFiles.contains(filePath) && !shouldIgnoreFileForAttach(document->filePath())) {
|
||||||
m_linkedFiles.append(filePath);
|
m_linkedFiles.append(filePath);
|
||||||
emit linkedFilesChanged();
|
emit linkedFilesChanged();
|
||||||
}
|
}
|
||||||
@ -537,4 +562,44 @@ void ChatRootView::setRecentFilePath(const QString &filePath)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ChatRootView::shouldIgnoreFileForAttach(const Utils::FilePath &filePath)
|
||||||
|
{
|
||||||
|
auto project = ProjectExplorer::ProjectManager::projectForFile(filePath);
|
||||||
|
if (project
|
||||||
|
&& m_clientInterface->contextManager()
|
||||||
|
->ignoreManager()
|
||||||
|
->shouldIgnore(filePath.toFSPathString(), project)) {
|
||||||
|
LOG_MESSAGE(QString("Ignoring file for attachment due to .qodeassistignore: %1")
|
||||||
|
.arg(filePath.toFSPathString()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -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
|
||||||
|
|
||||||
@ -78,6 +83,14 @@ public:
|
|||||||
|
|
||||||
QString chatFileName() const;
|
QString chatFileName() const;
|
||||||
void setRecentFilePath(const QString &filePath);
|
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:
|
public slots:
|
||||||
void sendMessage(const QString &message);
|
void sendMessage(const QString &message);
|
||||||
@ -94,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;
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -27,13 +27,38 @@ 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 real listViewContentY: 0
|
||||||
|
|
||||||
|
signal resetChatToMessage(int index)
|
||||||
|
|
||||||
height: msgColumn.implicitHeight + 10
|
height: msgColumn.implicitHeight + 10
|
||||||
radius: 8
|
radius: 8
|
||||||
color: isUserMessage ? palette.alternateBase
|
color: isUserMessage ? palette.alternateBase
|
||||||
: palette.base
|
: palette.base
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
id: mouse
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: msgColumn
|
id: msgColumn
|
||||||
|
|
||||||
@ -77,6 +102,8 @@ Rectangle {
|
|||||||
id: codeBlockComponent
|
id: codeBlockComponent
|
||||||
CodeBlockComponent {
|
CodeBlockComponent {
|
||||||
itemData: msgCreatorDelegate.modelData
|
itemData: msgCreatorDelegate.modelData
|
||||||
|
blockStart: root.y + msgCreatorDelegate.y
|
||||||
|
currentContentY: root.listViewContentY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,16 +155,48 @@ Rectangle {
|
|||||||
visible: root.isUserMessage
|
visible: root.isUserMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: stopButtonId
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
right: parent.right
|
||||||
|
top: parent.top
|
||||||
|
}
|
||||||
|
|
||||||
|
text: qsTr("ResetTo")
|
||||||
|
visible: root.isUserMessage && mouse.hovered
|
||||||
|
onClicked: function() {
|
||||||
|
root.resetChatToMessage(root.messageIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
component TextComponent : TextBlock {
|
component TextComponent : TextBlock {
|
||||||
required property var itemData
|
required property var itemData
|
||||||
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
|
||||||
@ -148,5 +207,7 @@ Rectangle {
|
|||||||
|
|
||||||
code: itemData.text
|
code: itemData.text
|
||||||
language: itemData.language
|
language: itemData.language
|
||||||
|
codeFontFamily: root.codeFontFamily
|
||||||
|
codeFontSize: root.codeFontSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,11 +92,25 @@ ChatRootView {
|
|||||||
|
|
||||||
delegate: ChatItem {
|
delegate: ChatItem {
|
||||||
required property var model
|
required property var model
|
||||||
|
required property int index
|
||||||
|
|
||||||
width: ListView.view.width - scroll.width
|
width: ListView.view.width - scroll.width
|
||||||
msgModel: root.chatModel.processMessageContent(model.content)
|
msgModel: root.chatModel.processMessageContent(model.content)
|
||||||
messageAttachments: model.attachments
|
messageAttachments: model.attachments
|
||||||
isUserMessage: model.roleType === ChatModel.User
|
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
|
||||||
|
messageInput.cursorPosition = model.content.length
|
||||||
|
root.chatModel.resetModelTo(index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
header: Item {
|
header: Item {
|
||||||
|
@ -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")
|
||||||
|
@ -25,7 +25,6 @@ TextEdit {
|
|||||||
readOnly: true
|
readOnly: true
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
textFormat: Text.StyledText
|
|
||||||
selectionColor: palette.highlight
|
selectionColor: palette.highlight
|
||||||
color: palette.text
|
color: palette.text
|
||||||
}
|
}
|
||||||
|
@ -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,31 @@ 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
|
||||||
|
{
|
||||||
|
return m_contextManager;
|
||||||
|
}
|
||||||
|
|
||||||
void LLMClientInterface::sendCompletionToClient(
|
void LLMClientInterface::sendCompletionToClient(
|
||||||
const QString &completion, const QJsonObject &request, bool isComplete)
|
const QString &completion, const QJsonObject &request, bool isComplete)
|
||||||
{
|
{
|
||||||
@ -322,7 +356,9 @@ void LLMClientInterface::sendCompletionToClient(
|
|||||||
completionItem[LanguageServerProtocol::textKey] = processedCompletion;
|
completionItem[LanguageServerProtocol::textKey] = processedCompletion;
|
||||||
QJsonObject range;
|
QJsonObject range;
|
||||||
range["start"] = position;
|
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::rangeKey] = range;
|
||||||
completionItem[LanguageServerProtocol::positionKey] = position;
|
completionItem[LanguageServerProtocol::positionKey] = position;
|
||||||
completions.append(completionItem);
|
completions.append(completionItem);
|
||||||
|
@ -62,6 +62,8 @@ public:
|
|||||||
// exposed for tests
|
// exposed for tests
|
||||||
void sendData(const QByteArray &data) override;
|
void sendData(const QByteArray &data) override;
|
||||||
|
|
||||||
|
Context::ContextManager *contextManager() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void startImpl() override;
|
void startImpl() override;
|
||||||
|
|
||||||
@ -75,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;
|
||||||
|
@ -29,6 +29,36 @@
|
|||||||
|
|
||||||
namespace QodeAssist {
|
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(
|
LLMSuggestion::LLMSuggestion(
|
||||||
const QList<Data> &suggestions, QTextDocument *sourceDocument, int currentCompletion)
|
const QList<Data> &suggestions, QTextDocument *sourceDocument, int currentCompletion)
|
||||||
: TextEditor::CyclicSuggestion(suggestions, sourceDocument, currentCompletion)
|
: TextEditor::CyclicSuggestion(suggestions, sourceDocument, currentCompletion)
|
||||||
@ -38,21 +68,28 @@ LLMSuggestion::LLMSuggestion(
|
|||||||
int startPos = data.range.begin.toPositionInDocument(sourceDocument);
|
int startPos = data.range.begin.toPositionInDocument(sourceDocument);
|
||||||
int endPos = data.range.end.toPositionInDocument(sourceDocument);
|
int endPos = data.range.end.toPositionInDocument(sourceDocument);
|
||||||
|
|
||||||
startPos = qBound(0, startPos, sourceDocument->characterCount() - 1);
|
startPos = qBound(0, startPos, sourceDocument->characterCount());
|
||||||
endPos = qBound(startPos, endPos, sourceDocument->characterCount() - 1);
|
endPos = qBound(startPos, endPos, sourceDocument->characterCount());
|
||||||
|
|
||||||
QTextCursor cursor(sourceDocument);
|
QTextCursor cursor(sourceDocument);
|
||||||
cursor.setPosition(startPos);
|
cursor.setPosition(startPos);
|
||||||
cursor.setPosition(endPos, QTextCursor::KeepAnchor);
|
|
||||||
|
|
||||||
QTextBlock block = cursor.block();
|
QTextBlock block = cursor.block();
|
||||||
QString blockText = block.text();
|
QString blockText = block.text();
|
||||||
|
|
||||||
int startPosInBlock = startPos - block.position();
|
int cursorPositionInBlock = cursor.positionInBlock();
|
||||||
int endPosInBlock = endPos - block.position();
|
|
||||||
|
|
||||||
blockText.replace(startPosInBlock, endPosInBlock - startPosInBlock, data.text);
|
QString rightText = blockText.mid(cursorPositionInBlock);
|
||||||
replacementDocument()->setPlainText(blockText);
|
|
||||||
|
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)
|
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);
|
int next = part == Word ? Utils::endOfNextWord(text, startPos) : text.indexOf('\n', startPos);
|
||||||
|
|
||||||
if (next == -1)
|
if (next == -1) {
|
||||||
|
if (part == Line) {
|
||||||
|
next = text.length();
|
||||||
|
} else {
|
||||||
return apply();
|
return apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (part == Line)
|
if (part == Line)
|
||||||
++next;
|
++next;
|
||||||
|
|
||||||
QString subText = text.mid(startPos, next - startPos);
|
QString subText = text.mid(startPos, next - startPos);
|
||||||
if (subText.isEmpty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
|
if (subText.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextBlock currentBlock = currentCursor.block();
|
||||||
|
QString textAfterCursor = currentBlock.text().mid(currentCursor.positionInBlock());
|
||||||
|
|
||||||
|
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);
|
currentCursor.insertText(subText);
|
||||||
|
|
||||||
if (const int seperatorPos = subText.lastIndexOf('\n'); seperatorPos >= 0) {
|
if (const int seperatorPos = subText.lastIndexOf('\n'); seperatorPos >= 0) {
|
||||||
const QString newCompletionText = text.mid(startPos + seperatorPos + 1);
|
const QString newCompletionText = text.mid(startPos + seperatorPos + 1);
|
||||||
if (!newCompletionText.isEmpty()) {
|
if (!newCompletionText.isEmpty()) {
|
||||||
const Utils::Text::Position newStart{int(range.begin.line + subText.count('\n')), 0};
|
const Utils::Text::Position newStart{int(range.begin.line + subText.count('\n')), 0};
|
||||||
const Utils::Text::Position
|
const Utils::Text::Position newEnd{newStart.line, int(newCompletionText.length())};
|
||||||
newEnd{newStart.line, int(subText.length() - seperatorPos - 1)};
|
|
||||||
const Utils::Text::Range newRange{newStart, newEnd};
|
const Utils::Text::Range newRange{newStart, newEnd};
|
||||||
const QList<Data> newSuggestion{{newRange, newEnd, newCompletionText}};
|
const QList<Data> newSuggestion{{newRange, newEnd, newCompletionText}};
|
||||||
widget->insertSuggestion(
|
widget->insertSuggestion(
|
||||||
std::make_unique<LLMSuggestion>(newSuggestion, widget->document(), 0));
|
std::make_unique<LLMSuggestion>(newSuggestion, widget->document(), 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
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
|
} // namespace QodeAssist
|
||||||
|
@ -40,5 +40,6 @@ public:
|
|||||||
bool applyWord(TextEditor::TextEditorWidget *widget) override;
|
bool applyWord(TextEditor::TextEditorWidget *widget) override;
|
||||||
bool applyLine(TextEditor::TextEditorWidget *widget) override;
|
bool applyLine(TextEditor::TextEditorWidget *widget) override;
|
||||||
bool applyPart(Part part, TextEditor::TextEditorWidget *widget);
|
bool applyPart(Part part, TextEditor::TextEditorWidget *widget);
|
||||||
|
bool apply() override;
|
||||||
};
|
};
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Id" : "qodeassist",
|
"Id" : "qodeassist",
|
||||||
"Name" : "QodeAssist",
|
"Name" : "QodeAssist",
|
||||||
"Version" : "0.5.7",
|
"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",
|
||||||
|
@ -2,5 +2,11 @@
|
|||||||
<qresource prefix="/">
|
<qresource prefix="/">
|
||||||
<file>resources/images/qoderassist-icon@2x.png</file>
|
<file>resources/images/qoderassist-icon@2x.png</file>
|
||||||
<file>resources/images/qoderassist-icon.png</file>
|
<file>resources/images/qoderassist-icon.png</file>
|
||||||
|
<file>resources/images/repeat-last-instruct-icon@2x.png</file>
|
||||||
|
<file>resources/images/repeat-last-instruct-icon.png</file>
|
||||||
|
<file>resources/images/improve-current-code-icon@2x.png</file>
|
||||||
|
<file>resources/images/improve-current-code-icon.png</file>
|
||||||
|
<file>resources/images/suggest-new-icon.png</file>
|
||||||
|
<file>resources/images/suggest-new-icon@2x.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
@ -49,6 +49,7 @@ namespace QodeAssist {
|
|||||||
|
|
||||||
QodeAssistClient::QodeAssistClient(LLMClientInterface *clientInterface)
|
QodeAssistClient::QodeAssistClient(LLMClientInterface *clientInterface)
|
||||||
: LanguageClient::Client(clientInterface)
|
: LanguageClient::Client(clientInterface)
|
||||||
|
, m_llmClient(clientInterface)
|
||||||
, m_recentCharCount(0)
|
, m_recentCharCount(0)
|
||||||
{
|
{
|
||||||
setName("QodeAssist");
|
setName("QodeAssist");
|
||||||
@ -152,6 +153,14 @@ void QodeAssistClient::requestCompletions(TextEditor::TextEditorWidget *editor)
|
|||||||
if (!isEnabled(project))
|
if (!isEnabled(project))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (m_llmClient->contextManager()
|
||||||
|
->ignoreManager()
|
||||||
|
->shouldIgnore(editor->textDocument()->filePath().toUrlishString(), project)) {
|
||||||
|
LOG_MESSAGE(QString("Ignoring file due to .qodeassistignore: %1")
|
||||||
|
.arg(editor->textDocument()->filePath().toUrlishString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MultiTextCursor cursor = editor->multiTextCursor();
|
MultiTextCursor cursor = editor->multiTextCursor();
|
||||||
if (cursor.hasMultipleCursors() || cursor.hasSelection() || editor->suggestionVisible())
|
if (cursor.hasMultipleCursors() || cursor.hasSelection() || editor->suggestionVisible())
|
||||||
return;
|
return;
|
||||||
@ -181,6 +190,14 @@ void QodeAssistClient::requestQuickRefactor(
|
|||||||
if (!isEnabled(project))
|
if (!isEnabled(project))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (m_llmClient->contextManager()
|
||||||
|
->ignoreManager()
|
||||||
|
->shouldIgnore(editor->textDocument()->filePath().toUrlishString(), project)) {
|
||||||
|
LOG_MESSAGE(QString("Ignoring file due to .qodeassistignore: %1")
|
||||||
|
.arg(editor->textDocument()->filePath().toUrlishString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!m_refactorHandler) {
|
if (!m_refactorHandler) {
|
||||||
m_refactorHandler = new QuickRefactorHandler(this);
|
m_refactorHandler = new QuickRefactorHandler(this);
|
||||||
connect(
|
connect(
|
||||||
@ -327,7 +344,6 @@ void QodeAssistClient::cleanupConnections()
|
|||||||
|
|
||||||
void QodeAssistClient::handleRefactoringResult(const RefactorResult &result)
|
void QodeAssistClient::handleRefactoringResult(const RefactorResult &result)
|
||||||
{
|
{
|
||||||
m_progressHandler.hideProgress();
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
LOG_MESSAGE(QString("Refactoring failed: %1").arg(result.errorMessage));
|
LOG_MESSAGE(QString("Refactoring failed: %1").arg(result.errorMessage));
|
||||||
return;
|
return;
|
||||||
@ -352,5 +368,6 @@ void QodeAssistClient::handleRefactoringResult(const RefactorResult &result)
|
|||||||
|
|
||||||
cursor.insertText(result.newText);
|
cursor.insertText(result.newText);
|
||||||
cursor.endEditBlock();
|
cursor.endEditBlock();
|
||||||
|
m_progressHandler.hideProgress();
|
||||||
}
|
}
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist
|
||||||
|
@ -69,6 +69,7 @@ private:
|
|||||||
CompletionProgressHandler m_progressHandler;
|
CompletionProgressHandler m_progressHandler;
|
||||||
EditorChatButtonHandler m_chatButtonHandler;
|
EditorChatButtonHandler m_chatButtonHandler;
|
||||||
QuickRefactorHandler *m_refactorHandler{nullptr};
|
QuickRefactorHandler *m_refactorHandler{nullptr};
|
||||||
|
LLMClientInterface *m_llmClient;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist
|
||||||
|
66
README.md
66
README.md
@ -2,7 +2,8 @@
|
|||||||
[](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml)
|
[](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml)
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
[](https://discord.gg/BGMkUsXUgf)
|
[](https://discord.gg/BGMkUsXUgf)
|
||||||
|
|
||||||
 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 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)
|
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. [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)
|
||||||
@ -35,6 +38,7 @@
|
|||||||
|
|
||||||
- AI-powered code completion
|
- AI-powered code completion
|
||||||
- Sharing IDE opened files with model context (disabled by default, need enable in settings)
|
- Sharing IDE opened files with model context (disabled by default, need enable in settings)
|
||||||
|
- Quick refactor code via fast chat command and opened files
|
||||||
- Chat functionality:
|
- Chat functionality:
|
||||||
- Side and Bottom panels
|
- Side and Bottom panels
|
||||||
- Chat history autosave and restore
|
- Chat history autosave and restore
|
||||||
@ -61,6 +65,11 @@ Join our Discord Community: Have questions or want to discuss QodeAssist? Join o
|
|||||||
<img src="https://github.com/user-attachments/assets/255a52f1-5cc0-4ca3-b05c-c4cf9cdbe25a" width="600" alt="QodeAssistPreview">
|
<img src="https://github.com/user-attachments/assets/255a52f1-5cc0-4ca3-b05c-c4cf9cdbe25a" width="600" alt="QodeAssistPreview">
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Quick refactor in code: (click to expand)</summary>
|
||||||
|
<img src="https://github.com/user-attachments/assets/4a9092e0-429f-41eb-8723-cbb202fd0a8c" width="600" alt="QodeAssistPreview">
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Multiline Code completion: (click to expand)</summary>
|
<summary>Multiline Code completion: (click to expand)</summary>
|
||||||
<img src="https://github.com/user-attachments/assets/c18dfbd2-8c54-4a7b-90d1-66e3bb51adb0" width="600" alt="QodeAssistPreview">
|
<img src="https://github.com/user-attachments/assets/c18dfbd2-8c54-4a7b-90d1-66e3bb51adb0" width="600" alt="QodeAssistPreview">
|
||||||
@ -195,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.
|
||||||
|
|
||||||
@ -228,9 +237,23 @@ 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.0 - 0.5.2 - 0.5.x
|
- 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
|
- QtCreator 15.0.1 - 0.4.8 - 0.5.1
|
||||||
- QtCreator 15.0.0 - 0.4.0 - 0.4.7
|
- QtCreator 15.0.0 - 0.4.0 - 0.4.7
|
||||||
- QtCreator 14.0.2 - 0.2.3 - 0.3.x
|
- QtCreator 14.0.2 - 0.2.3 - 0.3.x
|
||||||
@ -244,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
|
||||||
|
|
||||||
@ -253,6 +277,34 @@ Linked files provide persistent context throughout the conversation:
|
|||||||
- on Linux with KDE Plasma: Ctrl + Alt + Q
|
- on Linux with KDE Plasma: Ctrl + Alt + Q
|
||||||
- To insert the full suggestion, you can use the TAB key
|
- To insert the full suggestion, you can use the TAB key
|
||||||
- To insert word of suggistion, you can use Alt + Right Arrow for Win/Lin, or Option + Right Arrow for Mac
|
- To insert word of suggistion, you can use Alt + Right Arrow for Win/Lin, or Option + Right Arrow for Mac
|
||||||
|
- To call Quick Refactor dialog, select some code or place cursor and press
|
||||||
|
- 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
|
## Troubleshooting
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ add_library(Context STATIC
|
|||||||
TokenUtils.hpp TokenUtils.cpp
|
TokenUtils.hpp TokenUtils.cpp
|
||||||
ProgrammingLanguage.hpp ProgrammingLanguage.cpp
|
ProgrammingLanguage.hpp ProgrammingLanguage.cpp
|
||||||
IContextManager.hpp
|
IContextManager.hpp
|
||||||
|
IgnoreManager.hpp IgnoreManager.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(Context
|
target_link_libraries(Context
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include "settings/GeneralSettings.hpp"
|
#include "settings/GeneralSettings.hpp"
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
#include <projectexplorer/project.h>
|
#include <projectexplorer/project.h>
|
||||||
|
#include <projectexplorer/projectmanager.h>
|
||||||
#include <projectexplorer/projectnodes.h>
|
#include <projectexplorer/projectnodes.h>
|
||||||
#include <texteditor/textdocument.h>
|
#include <texteditor/textdocument.h>
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ namespace QodeAssist::Context {
|
|||||||
|
|
||||||
ContextManager::ContextManager(QObject *parent)
|
ContextManager::ContextManager(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
|
, m_ignoreManager(new IgnoreManager(this))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
QString ContextManager::readFile(const QString &filePath) const
|
QString ContextManager::readFile(const QString &filePath) const
|
||||||
@ -52,6 +54,13 @@ QList<ContentFile> ContextManager::getContentFiles(const QStringList &filePaths)
|
|||||||
{
|
{
|
||||||
QList<ContentFile> files;
|
QList<ContentFile> files;
|
||||||
for (const QString &path : filePaths) {
|
for (const QString &path : filePaths) {
|
||||||
|
auto project = ProjectExplorer::ProjectManager::projectForFile(
|
||||||
|
Utils::FilePath::fromString(path));
|
||||||
|
if (project && m_ignoreManager->shouldIgnore(path, project)) {
|
||||||
|
LOG_MESSAGE(QString("Ignoring file in context due to .qodeassistignore: %1").arg(path));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
ContentFile contentFile = createContentFile(path);
|
ContentFile contentFile = createContentFile(path);
|
||||||
files.append(contentFile);
|
files.append(contentFile);
|
||||||
}
|
}
|
||||||
@ -121,6 +130,14 @@ QList<QPair<QString, QString>> ContextManager::openedFiles(const QStringList exc
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto filePath = textDocument->filePath().toUrlishString();
|
auto filePath = textDocument->filePath().toUrlishString();
|
||||||
|
|
||||||
|
auto project = ProjectExplorer::ProjectManager::projectForFile(textDocument->filePath());
|
||||||
|
if (project && m_ignoreManager->shouldIgnore(filePath, project)) {
|
||||||
|
LOG_MESSAGE(
|
||||||
|
QString("Ignoring file in context due to .qodeassistignore: %1").arg(filePath));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!excludeFiles.contains(filePath)) {
|
if (!excludeFiles.contains(filePath)) {
|
||||||
files.append({filePath, textDocument->plainText()});
|
files.append({filePath, textDocument->plainText()});
|
||||||
}
|
}
|
||||||
@ -144,6 +161,13 @@ QString ContextManager::openedFilesContext(const QStringList excludeFiles)
|
|||||||
if (excludeFiles.contains(filePath))
|
if (excludeFiles.contains(filePath))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
auto project = ProjectExplorer::ProjectManager::projectForFile(textDocument->filePath());
|
||||||
|
if (project && m_ignoreManager->shouldIgnore(filePath, project)) {
|
||||||
|
LOG_MESSAGE(
|
||||||
|
QString("Ignoring file in context due to .qodeassistignore: %1").arg(filePath));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
context += QString("File: %1\n").arg(filePath);
|
context += QString("File: %1\n").arg(filePath);
|
||||||
context += textDocument->plainText();
|
context += textDocument->plainText();
|
||||||
|
|
||||||
@ -153,4 +177,9 @@ QString ContextManager::openedFilesContext(const QStringList excludeFiles)
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IgnoreManager *ContextManager::ignoreManager() const
|
||||||
|
{
|
||||||
|
return m_ignoreManager;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
} // namespace QodeAssist::Context
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#include "ContentFile.hpp"
|
#include "ContentFile.hpp"
|
||||||
#include "IContextManager.hpp"
|
#include "IContextManager.hpp"
|
||||||
|
#include "IgnoreManager.hpp"
|
||||||
#include "ProgrammingLanguage.hpp"
|
#include "ProgrammingLanguage.hpp"
|
||||||
|
|
||||||
namespace ProjectExplorer {
|
namespace ProjectExplorer {
|
||||||
@ -49,6 +50,11 @@ public:
|
|||||||
bool isSpecifyCompletion(const DocumentInfo &documentInfo) const override;
|
bool isSpecifyCompletion(const DocumentInfo &documentInfo) const override;
|
||||||
QList<QPair<QString, QString>> openedFiles(const QStringList excludeFiles = QStringList{}) const;
|
QList<QPair<QString, QString>> openedFiles(const QStringList excludeFiles = QStringList{}) const;
|
||||||
QString openedFilesContext(const QStringList excludeFiles = QStringList{});
|
QString openedFilesContext(const QStringList excludeFiles = QStringList{});
|
||||||
|
|
||||||
|
IgnoreManager *ignoreManager() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
IgnoreManager *m_ignoreManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
} // namespace QodeAssist::Context
|
||||||
|
274
context/IgnoreManager.cpp
Normal file
274
context/IgnoreManager.cpp
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IgnoreManager.hpp"
|
||||||
|
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <projectexplorer/projectmanager.h>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QTextStream>
|
||||||
|
|
||||||
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
IgnoreManager::IgnoreManager(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
auto projectManager = ProjectExplorer::ProjectManager::instance();
|
||||||
|
if (projectManager) {
|
||||||
|
connect(
|
||||||
|
projectManager,
|
||||||
|
&ProjectExplorer::ProjectManager::projectRemoved,
|
||||||
|
this,
|
||||||
|
&IgnoreManager::removeIgnorePatterns);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(
|
||||||
|
QCoreApplication::instance(),
|
||||||
|
&QCoreApplication::aboutToQuit,
|
||||||
|
this,
|
||||||
|
&IgnoreManager::cleanupConnections);
|
||||||
|
}
|
||||||
|
|
||||||
|
IgnoreManager::~IgnoreManager()
|
||||||
|
{
|
||||||
|
cleanupConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IgnoreManager::cleanupConnections()
|
||||||
|
{
|
||||||
|
QList<ProjectExplorer::Project *> projects = m_projectConnections.keys();
|
||||||
|
for (ProjectExplorer::Project *project : projects) {
|
||||||
|
if (project) {
|
||||||
|
disconnect(m_projectConnections.take(project));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_projectConnections.clear();
|
||||||
|
m_projectIgnorePatterns.clear();
|
||||||
|
m_ignoreCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IgnoreManager::shouldIgnore(const QString &filePath, ProjectExplorer::Project *project) const
|
||||||
|
{
|
||||||
|
if (!project)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!m_projectIgnorePatterns.contains(project)) {
|
||||||
|
const_cast<IgnoreManager *>(this)->reloadIgnorePatterns(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList &patterns = m_projectIgnorePatterns[project];
|
||||||
|
if (patterns.isEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QDir projectDir(project->projectDirectory().toUrlishString());
|
||||||
|
QString relativePath = projectDir.relativeFilePath(filePath);
|
||||||
|
|
||||||
|
return matchesIgnorePatterns(relativePath, patterns);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IgnoreManager::matchesIgnorePatterns(const QString &path, const QStringList &patterns) const
|
||||||
|
{
|
||||||
|
QString cacheKey = path + ":" + patterns.join("|");
|
||||||
|
if (m_ignoreCache.contains(cacheKey))
|
||||||
|
return m_ignoreCache[cacheKey];
|
||||||
|
|
||||||
|
bool result = isPathExcluded(path, patterns);
|
||||||
|
m_ignoreCache.insert(cacheKey, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IgnoreManager::isPathExcluded(const QString &path, const QStringList &patterns) const
|
||||||
|
{
|
||||||
|
bool excluded = false;
|
||||||
|
|
||||||
|
for (const QString &pattern : patterns) {
|
||||||
|
if (pattern.isEmpty() || pattern.startsWith('#'))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool isNegative = pattern.startsWith('!');
|
||||||
|
QString actualPattern = isNegative ? pattern.mid(1) : pattern;
|
||||||
|
|
||||||
|
bool matches = matchPathWithPattern(path, actualPattern);
|
||||||
|
|
||||||
|
if (matches) {
|
||||||
|
excluded = !isNegative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return excluded;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IgnoreManager::matchPathWithPattern(const QString &path, const QString &pattern) const
|
||||||
|
{
|
||||||
|
QString adjustedPattern = pattern.trimmed();
|
||||||
|
|
||||||
|
bool matchFromRoot = adjustedPattern.startsWith('/');
|
||||||
|
if (matchFromRoot)
|
||||||
|
adjustedPattern = adjustedPattern.mid(1);
|
||||||
|
|
||||||
|
bool matchDirOnly = adjustedPattern.endsWith('/');
|
||||||
|
if (matchDirOnly)
|
||||||
|
adjustedPattern.chop(1);
|
||||||
|
|
||||||
|
QString regexPattern = QRegularExpression::escape(adjustedPattern);
|
||||||
|
|
||||||
|
regexPattern.replace("\\*\\*", ".*");
|
||||||
|
|
||||||
|
regexPattern.replace("\\*", "[^/]*");
|
||||||
|
|
||||||
|
regexPattern.replace("\\?", ".");
|
||||||
|
|
||||||
|
if (matchFromRoot)
|
||||||
|
regexPattern = QString("^%1").arg(regexPattern);
|
||||||
|
else
|
||||||
|
regexPattern = QString("(^|/)%1").arg(regexPattern);
|
||||||
|
|
||||||
|
if (matchDirOnly)
|
||||||
|
regexPattern = QString("%1$").arg(regexPattern);
|
||||||
|
else
|
||||||
|
regexPattern = QString("%1($|/)").arg(regexPattern);
|
||||||
|
|
||||||
|
QRegularExpression regex(regexPattern);
|
||||||
|
QRegularExpressionMatch match = regex.match(path);
|
||||||
|
return match.hasMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList IgnoreManager::loadIgnorePatterns(ProjectExplorer::Project *project)
|
||||||
|
{
|
||||||
|
QStringList patterns;
|
||||||
|
if (!project)
|
||||||
|
return patterns;
|
||||||
|
|
||||||
|
QString ignoreFile = ignoreFilePath(project);
|
||||||
|
if (ignoreFile.isEmpty() || !QFile::exists(ignoreFile)) {
|
||||||
|
// LOG_MESSAGE(
|
||||||
|
// QString("No .qodeassistignore file found for project: %1").arg(project->displayName()));
|
||||||
|
return patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(ignoreFile);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
LOG_MESSAGE(QString("Could not open .qodeassistignore file: %1").arg(ignoreFile));
|
||||||
|
return patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextStream in(&file);
|
||||||
|
while (!in.atEnd()) {
|
||||||
|
QString line = in.readLine().trimmed();
|
||||||
|
if (!line.isEmpty() && !line.startsWith('#'))
|
||||||
|
patterns << line;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_MESSAGE(QString("Successfully loaded .qodeassistignore file: %1 with %2 patterns")
|
||||||
|
.arg(ignoreFile)
|
||||||
|
.arg(patterns.size()));
|
||||||
|
|
||||||
|
return patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IgnoreManager::reloadIgnorePatterns(ProjectExplorer::Project *project)
|
||||||
|
{
|
||||||
|
if (!project)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QStringList patterns = loadIgnorePatterns(project);
|
||||||
|
m_projectIgnorePatterns[project] = patterns;
|
||||||
|
|
||||||
|
QStringList keysToRemove;
|
||||||
|
QString projectPath = project->projectDirectory().toUrlishString();
|
||||||
|
for (auto it = m_ignoreCache.begin(); it != m_ignoreCache.end(); ++it) {
|
||||||
|
if (it.key().contains(projectPath))
|
||||||
|
keysToRemove << it.key();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QString &key : keysToRemove)
|
||||||
|
m_ignoreCache.remove(key);
|
||||||
|
|
||||||
|
if (!m_projectConnections.contains(project)) {
|
||||||
|
QPointer<ProjectExplorer::Project> projectPtr(project);
|
||||||
|
auto connection = connect(project, &QObject::destroyed, this, [this, projectPtr]() {
|
||||||
|
if (projectPtr) {
|
||||||
|
m_projectIgnorePatterns.remove(projectPtr);
|
||||||
|
m_projectConnections.remove(projectPtr);
|
||||||
|
|
||||||
|
QStringList keysToRemove;
|
||||||
|
for (auto it = m_ignoreCache.begin(); it != m_ignoreCache.end(); ++it) {
|
||||||
|
if (it.key().contains(projectPtr->projectDirectory().toUrlishString()))
|
||||||
|
keysToRemove << it.key();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QString &key : keysToRemove)
|
||||||
|
m_ignoreCache.remove(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
m_projectConnections[project] = connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IgnoreManager::removeIgnorePatterns(ProjectExplorer::Project *project)
|
||||||
|
{
|
||||||
|
m_projectIgnorePatterns.remove(project);
|
||||||
|
|
||||||
|
QStringList keysToRemove;
|
||||||
|
for (auto it = m_ignoreCache.begin(); it != m_ignoreCache.end(); ++it) {
|
||||||
|
if (it.key().contains(project->projectDirectory().toUrlishString()))
|
||||||
|
keysToRemove << it.key();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QString &key : keysToRemove)
|
||||||
|
m_ignoreCache.remove(key);
|
||||||
|
|
||||||
|
if (m_projectConnections.contains(project)) {
|
||||||
|
disconnect(m_projectConnections[project]);
|
||||||
|
m_projectConnections.remove(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_MESSAGE(QString("Removed ignore patterns for project: %1").arg(project->displayName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void IgnoreManager::reloadAllPatterns()
|
||||||
|
{
|
||||||
|
QList<ProjectExplorer::Project *> projects = m_projectIgnorePatterns.keys();
|
||||||
|
|
||||||
|
for (ProjectExplorer::Project *project : projects) {
|
||||||
|
if (project) {
|
||||||
|
reloadIgnorePatterns(project);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ignoreCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString IgnoreManager::ignoreFilePath(ProjectExplorer::Project *project) const
|
||||||
|
{
|
||||||
|
if (!project) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return project->projectDirectory().toUrlishString() + "/.qodeassistignore";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
62
context/IgnoreManager.hpp
Normal file
62
context/IgnoreManager.hpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
namespace ProjectExplorer {
|
||||||
|
class Project;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class IgnoreManager : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit IgnoreManager(QObject *parent = nullptr);
|
||||||
|
~IgnoreManager() override;
|
||||||
|
|
||||||
|
bool shouldIgnore(const QString &filePath, ProjectExplorer::Project *project = nullptr) const;
|
||||||
|
void reloadIgnorePatterns(ProjectExplorer::Project *project);
|
||||||
|
void removeIgnorePatterns(ProjectExplorer::Project *project);
|
||||||
|
|
||||||
|
void reloadAllPatterns();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void cleanupConnections();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool matchesIgnorePatterns(const QString &path, const QStringList &patterns) const;
|
||||||
|
bool isPathExcluded(const QString &path, const QStringList &patterns) const;
|
||||||
|
bool matchPathWithPattern(const QString &path, const QString &pattern) const;
|
||||||
|
QStringList loadIgnorePatterns(ProjectExplorer::Project *project);
|
||||||
|
QString ignoreFilePath(ProjectExplorer::Project *project) const;
|
||||||
|
|
||||||
|
QHash<ProjectExplorer::Project *, QStringList> m_projectIgnorePatterns;
|
||||||
|
mutable QHash<QString, bool> m_ignoreCache;
|
||||||
|
QHash<ProjectExplorer::Project *, QMetaObject::Connection> m_projectConnections;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
@ -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,7 +93,11 @@ 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(
|
||||||
|
reply,
|
||||||
|
&QNetworkReply::finished,
|
||||||
|
this,
|
||||||
|
[this, reply, requestId]() {
|
||||||
m_activeRequests.remove(requestId);
|
m_activeRequests.remove(requestId);
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
QString errorMessage = reply->errorString();
|
QString errorMessage = reply->errorString();
|
||||||
@ -71,8 +113,8 @@ void RequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &
|
|||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
|
@ -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,
|
||||||
|
46
providers/CodestralProvider.cpp
Normal file
46
providers/CodestralProvider.cpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "CodestralProvider.hpp"
|
||||||
|
|
||||||
|
#include "settings/ProviderSettings.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
|
QString CodestralProvider::name() const
|
||||||
|
{
|
||||||
|
return "Codestral";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CodestralProvider::url() const
|
||||||
|
{
|
||||||
|
return "https://codestral.mistral.ai";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CodestralProvider::supportsModelListing() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CodestralProvider::apiKey() const
|
||||||
|
{
|
||||||
|
return Settings::providerSettings().codestralApiKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Providers
|
35
providers/CodestralProvider.hpp
Normal file
35
providers/CodestralProvider.hpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "MistralAIProvider.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
|
class CodestralProvider : public MistralAIProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString name() const override;
|
||||||
|
QString url() const override;
|
||||||
|
bool supportsModelListing() const override;
|
||||||
|
QString apiKey() const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Providers
|
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include "llmcore/ProvidersManager.hpp"
|
#include "llmcore/ProvidersManager.hpp"
|
||||||
#include "providers/ClaudeProvider.hpp"
|
#include "providers/ClaudeProvider.hpp"
|
||||||
|
#include "providers/CodestralProvider.hpp"
|
||||||
#include "providers/GoogleAIProvider.hpp"
|
#include "providers/GoogleAIProvider.hpp"
|
||||||
#include "providers/LMStudioProvider.hpp"
|
#include "providers/LMStudioProvider.hpp"
|
||||||
#include "providers/LlamaCppProvider.hpp"
|
#include "providers/LlamaCppProvider.hpp"
|
||||||
@ -44,6 +45,7 @@ inline void registerProviders()
|
|||||||
providerManager.registerProvider<MistralAIProvider>();
|
providerManager.registerProvider<MistralAIProvider>();
|
||||||
providerManager.registerProvider<GoogleAIProvider>();
|
providerManager.registerProvider<GoogleAIProvider>();
|
||||||
providerManager.registerProvider<LlamaCppProvider>();
|
providerManager.registerProvider<LlamaCppProvider>();
|
||||||
|
providerManager.registerProvider<CodestralProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
@ -56,6 +56,7 @@
|
|||||||
#include "settings/ProjectSettingsPanel.hpp"
|
#include "settings/ProjectSettingsPanel.hpp"
|
||||||
#include "settings/SettingsConstants.hpp"
|
#include "settings/SettingsConstants.hpp"
|
||||||
#include "templates/Templates.hpp"
|
#include "templates/Templates.hpp"
|
||||||
|
#include "widgets/QuickRefactorDialog.hpp"
|
||||||
#include <coreplugin/actionmanager/actioncontainer.h>
|
#include <coreplugin/actionmanager/actioncontainer.h>
|
||||||
#include <coreplugin/actionmanager/actionmanager.h>
|
#include <coreplugin/actionmanager/actionmanager.h>
|
||||||
#include <texteditor/textdocument.h>
|
#include <texteditor/textdocument.h>
|
||||||
@ -148,17 +149,17 @@ public:
|
|||||||
quickRefactorAction.setIcon(QCODEASSIST_ICON.icon());
|
quickRefactorAction.setIcon(QCODEASSIST_ICON.icon());
|
||||||
quickRefactorAction.addOnTriggered(this, [this] {
|
quickRefactorAction.addOnTriggered(this, [this] {
|
||||||
if (auto editor = TextEditor::TextEditorWidget::currentTextEditorWidget()) {
|
if (auto editor = TextEditor::TextEditorWidget::currentTextEditorWidget()) {
|
||||||
bool ok;
|
|
||||||
if (m_qodeAssistClient && m_qodeAssistClient->reachable()) {
|
if (m_qodeAssistClient && m_qodeAssistClient->reachable()) {
|
||||||
QString instructions = QInputDialog::getText(
|
QuickRefactorDialog
|
||||||
Core::ICore::dialogParent(),
|
dialog(Core::ICore::dialogParent(), m_lastRefactorInstructions);
|
||||||
Tr::tr("Quick Refactor"),
|
|
||||||
Tr::tr("Enter refactoring instructions:"),
|
if (dialog.exec() == QDialog::Accepted) {
|
||||||
QLineEdit::Normal,
|
QString instructions = dialog.instructions();
|
||||||
QString(),
|
if (!instructions.isEmpty()) {
|
||||||
&ok);
|
m_lastRefactorInstructions = instructions;
|
||||||
if (ok)
|
|
||||||
m_qodeAssistClient->requestQuickRefactor(editor, instructions);
|
m_qodeAssistClient->requestQuickRefactor(editor, instructions);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "The QodeAssist is not ready. Please check your connection and "
|
qWarning() << "The QodeAssist is not ready. Please check your connection and "
|
||||||
"settings.";
|
"settings.";
|
||||||
@ -235,6 +236,7 @@ private:
|
|||||||
QPointer<Chat::NavigationPanel> m_navigationPanel;
|
QPointer<Chat::NavigationPanel> m_navigationPanel;
|
||||||
QPointer<PluginUpdater> m_updater;
|
QPointer<PluginUpdater> m_updater;
|
||||||
UpdateStatusWidget *m_statusWidget{nullptr};
|
UpdateStatusWidget *m_statusWidget{nullptr};
|
||||||
|
QString m_lastRefactorInstructions;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Internal
|
} // namespace QodeAssist::Internal
|
||||||
|
BIN
resources/images/improve-current-code-icon.png
Normal file
BIN
resources/images/improve-current-code-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/improve-current-code-icon@2x.png
Normal file
BIN
resources/images/improve-current-code-icon@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
BIN
resources/images/repeat-last-instruct-icon.png
Normal file
BIN
resources/images/repeat-last-instruct-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/repeat-last-instruct-icon@2x.png
Normal file
BIN
resources/images/repeat-last-instruct-icon@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
BIN
resources/images/suggest-new-icon.png
Normal file
BIN
resources/images/suggest-new-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 963 B |
BIN
resources/images/suggest-new-icon@2x.png
Normal file
BIN
resources/images/suggest-new-icon@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
@ -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"
|
||||||
@ -47,8 +49,8 @@ ChatAssistantSettings::ChatAssistantSettings()
|
|||||||
chatTokensThreshold.setLabelText(Tr::tr("Chat history token limit:"));
|
chatTokensThreshold.setLabelText(Tr::tr("Chat history token limit:"));
|
||||||
chatTokensThreshold.setToolTip(Tr::tr("Maximum number of tokens in chat history. When "
|
chatTokensThreshold.setToolTip(Tr::tr("Maximum number of tokens in chat history. When "
|
||||||
"exceeded, oldest messages will be removed."));
|
"exceeded, oldest messages will be removed."));
|
||||||
chatTokensThreshold.setRange(1, 900000);
|
chatTokensThreshold.setRange(1, 99999999);
|
||||||
chatTokensThreshold.setDefaultValue(8000);
|
chatTokensThreshold.setDefaultValue(20000);
|
||||||
|
|
||||||
linkOpenFiles.setSettingsKey(Constants::CA_LINK_OPEN_FILES);
|
linkOpenFiles.setSettingsKey(Constants::CA_LINK_OPEN_FILES);
|
||||||
linkOpenFiles.setLabelText(Tr::tr("Sync open files with assistant by default"));
|
linkOpenFiles.setLabelText(Tr::tr("Sync open files with assistant by default"));
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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};
|
||||||
|
|
||||||
|
@ -87,6 +87,14 @@ ProviderSettings::ProviderSettings()
|
|||||||
mistralAiApiKey.setDefaultValue("");
|
mistralAiApiKey.setDefaultValue("");
|
||||||
mistralAiApiKey.setAutoApply(true);
|
mistralAiApiKey.setAutoApply(true);
|
||||||
|
|
||||||
|
codestralApiKey.setSettingsKey(Constants::CODESTRAL_API_KEY);
|
||||||
|
codestralApiKey.setLabelText(Tr::tr("Codestral API Key:"));
|
||||||
|
codestralApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||||
|
codestralApiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
||||||
|
codestralApiKey.setHistoryCompleter(Constants::CODESTRAL_API_KEY_HISTORY);
|
||||||
|
codestralApiKey.setDefaultValue("");
|
||||||
|
codestralApiKey.setAutoApply(true);
|
||||||
|
|
||||||
// GoogleAI Settings
|
// GoogleAI Settings
|
||||||
googleAiApiKey.setSettingsKey(Constants::GOOGLE_AI_API_KEY);
|
googleAiApiKey.setSettingsKey(Constants::GOOGLE_AI_API_KEY);
|
||||||
googleAiApiKey.setLabelText(Tr::tr("Google AI API Key:"));
|
googleAiApiKey.setLabelText(Tr::tr("Google AI API Key:"));
|
||||||
@ -125,7 +133,7 @@ ProviderSettings::ProviderSettings()
|
|||||||
Space{8},
|
Space{8},
|
||||||
Group{title(Tr::tr("Claude Settings")), Column{claudeApiKey}},
|
Group{title(Tr::tr("Claude Settings")), Column{claudeApiKey}},
|
||||||
Space{8},
|
Space{8},
|
||||||
Group{title(Tr::tr("Mistral AI Settings")), Column{mistralAiApiKey}},
|
Group{title(Tr::tr("Mistral AI Settings")), Column{mistralAiApiKey, codestralApiKey}},
|
||||||
Space{8},
|
Space{8},
|
||||||
Group{title(Tr::tr("Google AI Settings")), Column{googleAiApiKey}},
|
Group{title(Tr::tr("Google AI Settings")), Column{googleAiApiKey}},
|
||||||
Space{8},
|
Space{8},
|
||||||
@ -149,6 +157,9 @@ void ProviderSettings::setupConnections()
|
|||||||
connect(&mistralAiApiKey, &ButtonAspect::changed, this, [this]() {
|
connect(&mistralAiApiKey, &ButtonAspect::changed, this, [this]() {
|
||||||
mistralAiApiKey.writeSettings();
|
mistralAiApiKey.writeSettings();
|
||||||
});
|
});
|
||||||
|
connect(&codestralApiKey, &ButtonAspect::changed, this, [this]() {
|
||||||
|
codestralApiKey.writeSettings();
|
||||||
|
});
|
||||||
connect(&googleAiApiKey, &ButtonAspect::changed, this, [this]() {
|
connect(&googleAiApiKey, &ButtonAspect::changed, this, [this]() {
|
||||||
googleAiApiKey.writeSettings();
|
googleAiApiKey.writeSettings();
|
||||||
});
|
});
|
||||||
|
@ -38,6 +38,7 @@ public:
|
|||||||
Utils::StringAspect claudeApiKey{this};
|
Utils::StringAspect claudeApiKey{this};
|
||||||
Utils::StringAspect openAiApiKey{this};
|
Utils::StringAspect openAiApiKey{this};
|
||||||
Utils::StringAspect mistralAiApiKey{this};
|
Utils::StringAspect mistralAiApiKey{this};
|
||||||
|
Utils::StringAspect codestralApiKey{this};
|
||||||
Utils::StringAspect googleAiApiKey{this};
|
Utils::StringAspect googleAiApiKey{this};
|
||||||
Utils::StringAspect ollamaBasicAuthApiKey{this};
|
Utils::StringAspect ollamaBasicAuthApiKey{this};
|
||||||
|
|
||||||
|
@ -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";
|
||||||
@ -101,6 +110,8 @@ const char OPEN_AI_API_KEY[] = "QodeAssist.openAiApiKey";
|
|||||||
const char OPEN_AI_API_KEY_HISTORY[] = "QodeAssist.openAiApiKeyHistory";
|
const char OPEN_AI_API_KEY_HISTORY[] = "QodeAssist.openAiApiKeyHistory";
|
||||||
const char MISTRAL_AI_API_KEY[] = "QodeAssist.mistralAiApiKey";
|
const char MISTRAL_AI_API_KEY[] = "QodeAssist.mistralAiApiKey";
|
||||||
const char MISTRAL_AI_API_KEY_HISTORY[] = "QodeAssist.mistralAiApiKeyHistory";
|
const char MISTRAL_AI_API_KEY_HISTORY[] = "QodeAssist.mistralAiApiKeyHistory";
|
||||||
|
const char CODESTRAL_API_KEY[] = "QodeAssist.codestralApiKey";
|
||||||
|
const char CODESTRAL_API_KEY_HISTORY[] = "QodeAssist.codestralApiKeyHistory";
|
||||||
const char GOOGLE_AI_API_KEY[] = "QodeAssist.googleAiApiKey";
|
const char GOOGLE_AI_API_KEY[] = "QodeAssist.googleAiApiKey";
|
||||||
const char GOOGLE_AI_API_KEY_HISTORY[] = "QodeAssist.googleAiApiKeyHistory";
|
const char GOOGLE_AI_API_KEY_HISTORY[] = "QodeAssist.googleAiApiKeyHistory";
|
||||||
const char OLLAMA_BASIC_AUTH_API_KEY[] = "QodeAssist.ollamaBasicAuthApiKey";
|
const char OLLAMA_BASIC_AUTH_API_KEY[] = "QodeAssist.ollamaBasicAuthApiKey";
|
||||||
@ -149,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
|
||||||
|
@ -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");
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
217
widgets/QuickRefactorDialog.cpp
Normal file
217
widgets/QuickRefactorDialog.cpp
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "QuickRefactorDialog.hpp"
|
||||||
|
#include "QodeAssisttr.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QFontMetrics>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPlainTextEdit>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QToolButton>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include <utils/theme/theme.h>
|
||||||
|
#include <utils/utilsicons.h>
|
||||||
|
|
||||||
|
namespace QodeAssist {
|
||||||
|
|
||||||
|
QuickRefactorDialog::QuickRefactorDialog(QWidget *parent, const QString &lastInstructions)
|
||||||
|
: QDialog(parent)
|
||||||
|
, m_lastInstructions(lastInstructions)
|
||||||
|
{
|
||||||
|
setWindowTitle(Tr::tr("Quick Refactor"));
|
||||||
|
setupUi();
|
||||||
|
|
||||||
|
QTimer::singleShot(0, this, &QuickRefactorDialog::updateDialogSize);
|
||||||
|
m_textEdit->installEventFilter(this);
|
||||||
|
updateDialogSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuickRefactorDialog::setupUi()
|
||||||
|
{
|
||||||
|
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||||
|
mainLayout->setContentsMargins(10, 10, 10, 10);
|
||||||
|
mainLayout->setSpacing(8);
|
||||||
|
|
||||||
|
QHBoxLayout *actionsLayout = new QHBoxLayout();
|
||||||
|
actionsLayout->setSpacing(4);
|
||||||
|
createActionButtons();
|
||||||
|
actionsLayout->addWidget(m_repeatButton);
|
||||||
|
actionsLayout->addWidget(m_improveButton);
|
||||||
|
actionsLayout->addWidget(m_alternativeButton);
|
||||||
|
actionsLayout->addStretch();
|
||||||
|
mainLayout->addLayout(actionsLayout);
|
||||||
|
|
||||||
|
m_instructionsLabel = new QLabel(Tr::tr("Enter refactoring instructions:"), this);
|
||||||
|
mainLayout->addWidget(m_instructionsLabel);
|
||||||
|
|
||||||
|
m_textEdit = new QPlainTextEdit(this);
|
||||||
|
m_textEdit->setMinimumHeight(100);
|
||||||
|
m_textEdit->setPlaceholderText(Tr::tr("Type your refactoring instructions here..."));
|
||||||
|
|
||||||
|
connect(m_textEdit, &QPlainTextEdit::textChanged, this, &QuickRefactorDialog::updateDialogSize);
|
||||||
|
|
||||||
|
mainLayout->addWidget(m_textEdit);
|
||||||
|
|
||||||
|
QDialogButtonBox *buttonBox
|
||||||
|
= new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
mainLayout->addWidget(buttonBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuickRefactorDialog::createActionButtons()
|
||||||
|
{
|
||||||
|
Utils::Icon REPEAT_ICON(
|
||||||
|
{{":/resources/images/repeat-last-instruct-icon.png", Utils::Theme::IconsBaseColor}});
|
||||||
|
Utils::Icon IMPROVE_ICON(
|
||||||
|
{{":/resources/images/improve-current-code-icon.png", Utils::Theme::IconsBaseColor}});
|
||||||
|
Utils::Icon ALTER_ICON(
|
||||||
|
{{":/resources/images/suggest-new-icon.png", Utils::Theme::IconsBaseColor}});
|
||||||
|
m_repeatButton = new QToolButton(this);
|
||||||
|
m_repeatButton->setIcon(REPEAT_ICON.icon());
|
||||||
|
m_repeatButton->setToolTip(Tr::tr("Repeat Last Instructions"));
|
||||||
|
m_repeatButton->setEnabled(!m_lastInstructions.isEmpty());
|
||||||
|
connect(m_repeatButton, &QToolButton::clicked, this, &QuickRefactorDialog::useLastInstructions);
|
||||||
|
|
||||||
|
m_improveButton = new QToolButton(this);
|
||||||
|
m_improveButton->setIcon(IMPROVE_ICON.icon());
|
||||||
|
m_improveButton->setToolTip(Tr::tr("Improve Current Code"));
|
||||||
|
connect(
|
||||||
|
m_improveButton, &QToolButton::clicked, this, &QuickRefactorDialog::useImproveCodeTemplate);
|
||||||
|
|
||||||
|
m_alternativeButton = new QToolButton(this);
|
||||||
|
m_alternativeButton->setIcon(ALTER_ICON.icon());
|
||||||
|
m_alternativeButton->setToolTip(Tr::tr("Suggest Alternative Solution"));
|
||||||
|
connect(
|
||||||
|
m_alternativeButton,
|
||||||
|
&QToolButton::clicked,
|
||||||
|
this,
|
||||||
|
&QuickRefactorDialog::useAlternativeSolutionTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QuickRefactorDialog::instructions() const
|
||||||
|
{
|
||||||
|
return m_textEdit->toPlainText();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuickRefactorDialog::setInstructions(const QString &instructions)
|
||||||
|
{
|
||||||
|
m_textEdit->setPlainText(instructions);
|
||||||
|
}
|
||||||
|
|
||||||
|
QuickRefactorDialog::Action QuickRefactorDialog::selectedAction() const
|
||||||
|
{
|
||||||
|
return m_selectedAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QuickRefactorDialog::eventFilter(QObject *watched, QEvent *event)
|
||||||
|
{
|
||||||
|
if (watched == m_textEdit && event->type() == QEvent::KeyPress) {
|
||||||
|
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||||
|
if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
|
||||||
|
if (keyEvent->modifiers() & Qt::ShiftModifier) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
accept();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QDialog::eventFilter(watched, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuickRefactorDialog::useLastInstructions()
|
||||||
|
{
|
||||||
|
if (!m_lastInstructions.isEmpty()) {
|
||||||
|
m_textEdit->setPlainText(m_lastInstructions);
|
||||||
|
m_selectedAction = Action::RepeatLast;
|
||||||
|
}
|
||||||
|
accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuickRefactorDialog::useImproveCodeTemplate()
|
||||||
|
{
|
||||||
|
m_textEdit->setPlainText(Tr::tr(
|
||||||
|
"Improve the selected code by enhancing readability, efficiency, and maintainability. "
|
||||||
|
"Follow best practices for C++/Qt and fix any potential issues."));
|
||||||
|
m_selectedAction = Action::ImproveCode;
|
||||||
|
accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuickRefactorDialog::useAlternativeSolutionTemplate()
|
||||||
|
{
|
||||||
|
m_textEdit->setPlainText(
|
||||||
|
Tr::tr("Suggest an alternative implementation approach for the selected code. "
|
||||||
|
"Provide a different solution that might be cleaner, more efficient, "
|
||||||
|
"or uses different Qt/C++ patterns or idioms."));
|
||||||
|
m_selectedAction = Action::AlternativeSolution;
|
||||||
|
accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuickRefactorDialog::updateDialogSize()
|
||||||
|
{
|
||||||
|
QString text = m_textEdit->toPlainText();
|
||||||
|
|
||||||
|
QFontMetrics fm(m_textEdit->font());
|
||||||
|
|
||||||
|
QStringList lines = text.split('\n');
|
||||||
|
int lineCount = lines.size();
|
||||||
|
|
||||||
|
if (lineCount <= 1) {
|
||||||
|
int singleLineHeight = fm.height() + 10;
|
||||||
|
m_textEdit->setMinimumHeight(singleLineHeight);
|
||||||
|
m_textEdit->setMaximumHeight(singleLineHeight);
|
||||||
|
} else {
|
||||||
|
m_textEdit->setMaximumHeight(QWIDGETSIZE_MAX);
|
||||||
|
|
||||||
|
int lineHeight = fm.height() + 2;
|
||||||
|
|
||||||
|
int textEditHeight = qMin(qMax(lineCount, 2) * lineHeight, 20 * lineHeight);
|
||||||
|
m_textEdit->setMinimumHeight(textEditHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxWidth = 500;
|
||||||
|
for (const QString &line : lines) {
|
||||||
|
int lineWidth = fm.horizontalAdvance(line) + 30;
|
||||||
|
maxWidth = qMax(maxWidth, qMin(lineWidth, 800));
|
||||||
|
}
|
||||||
|
|
||||||
|
QScreen *screen = QApplication::primaryScreen();
|
||||||
|
QRect screenGeometry = screen->availableGeometry();
|
||||||
|
|
||||||
|
int newWidth = qMin(maxWidth + 40, screenGeometry.width() * 3 / 4);
|
||||||
|
|
||||||
|
int newHeight;
|
||||||
|
if (lineCount <= 1) {
|
||||||
|
newHeight = 150;
|
||||||
|
} else {
|
||||||
|
newHeight = m_textEdit->minimumHeight() + 150;
|
||||||
|
}
|
||||||
|
newHeight = qMin(newHeight, screenGeometry.height() * 3 / 4);
|
||||||
|
|
||||||
|
resize(newWidth, newHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist
|
69
widgets/QuickRefactorDialog.hpp
Normal file
69
widgets/QuickRefactorDialog.hpp
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class QPlainTextEdit;
|
||||||
|
class QToolButton;
|
||||||
|
class QLabel;
|
||||||
|
|
||||||
|
namespace QodeAssist {
|
||||||
|
|
||||||
|
class QuickRefactorDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum class Action { Custom, RepeatLast, ImproveCode, AlternativeSolution };
|
||||||
|
|
||||||
|
explicit QuickRefactorDialog(
|
||||||
|
QWidget *parent = nullptr, const QString &lastInstructions = QString());
|
||||||
|
~QuickRefactorDialog() override = default;
|
||||||
|
|
||||||
|
QString instructions() const;
|
||||||
|
void setInstructions(const QString &instructions);
|
||||||
|
|
||||||
|
Action selectedAction() const;
|
||||||
|
|
||||||
|
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void useLastInstructions();
|
||||||
|
void useImproveCodeTemplate();
|
||||||
|
void useAlternativeSolutionTemplate();
|
||||||
|
void updateDialogSize();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupUi();
|
||||||
|
void createActionButtons();
|
||||||
|
|
||||||
|
QPlainTextEdit *m_textEdit;
|
||||||
|
QToolButton *m_repeatButton;
|
||||||
|
QToolButton *m_improveButton;
|
||||||
|
QToolButton *m_alternativeButton;
|
||||||
|
QLabel *m_instructionsLabel;
|
||||||
|
|
||||||
|
Action m_selectedAction = Action::Custom;
|
||||||
|
QString m_lastInstructions;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist
|
Loading…
x
Reference in New Issue
Block a user