mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-02-15 11:33:02 -05:00
feat: Popup to show current project rules (#241)
* feat: Popup to show current project rules * feat: Add icon to show project rules button * feat: Add counter for active rules
This commit is contained in:
@ -19,6 +19,7 @@ qt_add_qml_module(QodeAssistChatView
|
|||||||
qml/parts/ErrorToast.qml
|
qml/parts/ErrorToast.qml
|
||||||
qml/ToolStatusItem.qml
|
qml/ToolStatusItem.qml
|
||||||
qml/FileEditChangesItem.qml
|
qml/FileEditChangesItem.qml
|
||||||
|
qml/parts/RulesViewer.qml
|
||||||
|
|
||||||
RESOURCES
|
RESOURCES
|
||||||
icons/attach-file-light.svg
|
icons/attach-file-light.svg
|
||||||
@ -35,6 +36,7 @@ qt_add_qml_module(QodeAssistChatView
|
|||||||
icons/window-unlock.svg
|
icons/window-unlock.svg
|
||||||
icons/chat-icon.svg
|
icons/chat-icon.svg
|
||||||
icons/chat-pause-icon.svg
|
icons/chat-pause-icon.svg
|
||||||
|
icons/rules-icon.svg
|
||||||
|
|
||||||
SOURCES
|
SOURCES
|
||||||
ChatWidget.hpp ChatWidget.cpp
|
ChatWidget.hpp ChatWidget.cpp
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
#include "ProjectSettings.hpp"
|
#include "ProjectSettings.hpp"
|
||||||
#include "context/ContextManager.hpp"
|
#include "context/ContextManager.hpp"
|
||||||
#include "context/TokenUtils.hpp"
|
#include "context/TokenUtils.hpp"
|
||||||
|
#include "llmcore/RulesLoader.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
@ -139,6 +140,14 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
|||||||
});
|
});
|
||||||
|
|
||||||
updateInputTokensCount();
|
updateInputTokensCount();
|
||||||
|
refreshRules();
|
||||||
|
|
||||||
|
// Refresh rules when project changes
|
||||||
|
connect(
|
||||||
|
ProjectExplorer::ProjectManager::instance(),
|
||||||
|
&ProjectExplorer::ProjectManager::startupProjectChanged,
|
||||||
|
this,
|
||||||
|
&ChatRootView::refreshRules);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatModel *ChatRootView::chatModel() const
|
ChatModel *ChatRootView::chatModel() const
|
||||||
@ -339,8 +348,7 @@ QString ChatRootView::getSuggestedFileName() const
|
|||||||
QFileInfo finalCheck(fullPath);
|
QFileInfo finalCheck(fullPath);
|
||||||
|
|
||||||
if (fileName.isEmpty() || finalCheck.exists() || !QFileInfo(finalCheck.path()).isWritable()) {
|
if (fileName.isEmpty() || finalCheck.exists() || !QFileInfo(finalCheck.path()).isWritable()) {
|
||||||
fileName = QString("chat_%1").arg(
|
fileName = QString("chat_%1").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm"));
|
||||||
QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileName;
|
return fileName;
|
||||||
@ -491,6 +499,25 @@ void ChatRootView::openChatHistoryFolder()
|
|||||||
QDesktopServices::openUrl(url);
|
QDesktopServices::openUrl(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatRootView::openRulesFolder()
|
||||||
|
{
|
||||||
|
auto project = ProjectExplorer::ProjectManager::startupProject();
|
||||||
|
if (!project) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString projectPath = project->projectDirectory().toFSPathString();
|
||||||
|
QString rulesPath = projectPath + "/.qodeassist/rules";
|
||||||
|
|
||||||
|
QDir dir(rulesPath);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
dir.mkpath(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl url = QUrl::fromLocalFile(dir.absolutePath());
|
||||||
|
QDesktopServices::openUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
void ChatRootView::updateInputTokensCount()
|
void ChatRootView::updateInputTokensCount()
|
||||||
{
|
{
|
||||||
int inputTokens = m_messageTokensCount;
|
int inputTokens = m_messageTokensCount;
|
||||||
@ -632,4 +659,49 @@ QString ChatRootView::lastErrorMessage() const
|
|||||||
return m_lastErrorMessage;
|
return m_lastErrorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariantList ChatRootView::activeRules() const
|
||||||
|
{
|
||||||
|
return m_activeRules;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ChatRootView::activeRulesCount() const
|
||||||
|
{
|
||||||
|
return m_activeRules.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatRootView::getRuleContent(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= m_activeRules.size())
|
||||||
|
return QString();
|
||||||
|
|
||||||
|
return LLMCore::RulesLoader::loadRuleFileContent(
|
||||||
|
m_activeRules[index].toMap()["filePath"].toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::refreshRules()
|
||||||
|
{
|
||||||
|
m_activeRules.clear();
|
||||||
|
|
||||||
|
auto project = LLMCore::RulesLoader::getActiveProject();
|
||||||
|
if (!project) {
|
||||||
|
emit activeRulesChanged();
|
||||||
|
emit activeRulesCountChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ruleFiles
|
||||||
|
= LLMCore::RulesLoader::getRuleFilesForProject(project, LLMCore::RulesContext::Chat);
|
||||||
|
|
||||||
|
for (const auto &ruleFile : ruleFiles) {
|
||||||
|
QVariantMap ruleMap;
|
||||||
|
ruleMap["filePath"] = ruleFile.filePath;
|
||||||
|
ruleMap["fileName"] = ruleFile.fileName;
|
||||||
|
ruleMap["category"] = ruleFile.category;
|
||||||
|
m_activeRules.append(ruleMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit activeRulesChanged();
|
||||||
|
emit activeRulesCountChanged();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@ -43,9 +43,10 @@ class ChatRootView : public QQuickItem
|
|||||||
Q_PROPERTY(int codeFontSize READ codeFontSize NOTIFY codeFontSizeChanged FINAL)
|
Q_PROPERTY(int codeFontSize READ codeFontSize NOTIFY codeFontSizeChanged FINAL)
|
||||||
Q_PROPERTY(int textFontSize READ textFontSize NOTIFY textFontSizeChanged FINAL)
|
Q_PROPERTY(int textFontSize READ textFontSize NOTIFY textFontSizeChanged FINAL)
|
||||||
Q_PROPERTY(int textFormat READ textFormat NOTIFY textFormatChanged FINAL)
|
Q_PROPERTY(int textFormat READ textFormat NOTIFY textFormatChanged FINAL)
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(bool isRequestInProgress READ isRequestInProgress NOTIFY isRequestInProgressChanged FINAL)
|
||||||
bool isRequestInProgress READ isRequestInProgress NOTIFY isRequestInProgressChanged FINAL)
|
|
||||||
Q_PROPERTY(QString lastErrorMessage READ lastErrorMessage NOTIFY lastErrorMessageChanged FINAL)
|
Q_PROPERTY(QString lastErrorMessage READ lastErrorMessage NOTIFY lastErrorMessageChanged FINAL)
|
||||||
|
Q_PROPERTY(QVariantList activeRules READ activeRules NOTIFY activeRulesChanged FINAL)
|
||||||
|
Q_PROPERTY(int activeRulesCount READ activeRulesCount NOTIFY activeRulesCountChanged FINAL)
|
||||||
|
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
@ -74,6 +75,7 @@ public:
|
|||||||
Q_INVOKABLE void calculateMessageTokensCount(const QString &message);
|
Q_INVOKABLE void calculateMessageTokensCount(const QString &message);
|
||||||
Q_INVOKABLE void setIsSyncOpenFiles(bool state);
|
Q_INVOKABLE void setIsSyncOpenFiles(bool state);
|
||||||
Q_INVOKABLE void openChatHistoryFolder();
|
Q_INVOKABLE void openChatHistoryFolder();
|
||||||
|
Q_INVOKABLE void openRulesFolder();
|
||||||
|
|
||||||
Q_INVOKABLE void updateInputTokensCount();
|
Q_INVOKABLE void updateInputTokensCount();
|
||||||
int inputTokensCount() const;
|
int inputTokensCount() const;
|
||||||
@ -99,6 +101,11 @@ public:
|
|||||||
void setRequestProgressStatus(bool state);
|
void setRequestProgressStatus(bool state);
|
||||||
|
|
||||||
QString lastErrorMessage() const;
|
QString lastErrorMessage() const;
|
||||||
|
|
||||||
|
QVariantList activeRules() const;
|
||||||
|
int activeRulesCount() const;
|
||||||
|
Q_INVOKABLE QString getRuleContent(int index);
|
||||||
|
Q_INVOKABLE void refreshRules();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void sendMessage(const QString &message);
|
void sendMessage(const QString &message);
|
||||||
@ -124,6 +131,8 @@ signals:
|
|||||||
void isRequestInProgressChanged();
|
void isRequestInProgressChanged();
|
||||||
|
|
||||||
void lastErrorMessageChanged();
|
void lastErrorMessageChanged();
|
||||||
|
void activeRulesChanged();
|
||||||
|
void activeRulesCountChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString getChatsHistoryDir() const;
|
QString getChatsHistoryDir() const;
|
||||||
@ -142,6 +151,7 @@ private:
|
|||||||
QList<Core::IEditor *> m_currentEditors;
|
QList<Core::IEditor *> m_currentEditors;
|
||||||
bool m_isRequestInProgress;
|
bool m_isRequestInProgress;
|
||||||
QString m_lastErrorMessage;
|
QString m_lastErrorMessage;
|
||||||
|
QVariantList m_activeRules;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
9
ChatView/icons/rules-icon.svg
Normal file
9
ChatView/icons/rules-icon.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M35.75 2.75H8.25C6.73122 2.75 5.5 3.98122 5.5 5.5V38.5C5.5 40.0188 6.73122 41.25 8.25 41.25H35.75C37.2688 41.25 38.5 40.0188 38.5 38.5V5.5C38.5 3.98122 37.2688 2.75 35.75 2.75Z" stroke="black" stroke-width="4"/>
|
||||||
|
<path d="M13.75 14.4375C14.8891 14.4375 15.8125 13.5141 15.8125 12.375C15.8125 11.2359 14.8891 10.3125 13.75 10.3125C12.6109 10.3125 11.6875 11.2359 11.6875 12.375C11.6875 13.5141 12.6109 14.4375 13.75 14.4375Z" fill="black"/>
|
||||||
|
<path d="M19.25 12.375H33" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M13.75 24.0625C14.8891 24.0625 15.8125 23.1391 15.8125 22C15.8125 20.8609 14.8891 19.9375 13.75 19.9375C12.6109 19.9375 11.6875 20.8609 11.6875 22C11.6875 23.1391 12.6109 24.0625 13.75 24.0625Z" fill="black"/>
|
||||||
|
<path d="M19.25 22H33" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M13.75 33.6875C14.8891 33.6875 15.8125 32.7641 15.8125 31.625C15.8125 30.4859 14.8891 29.5625 13.75 29.5625C12.6109 29.5625 11.6875 30.4859 11.6875 31.625C11.6875 32.7641 12.6109 33.6875 13.75 33.6875Z" fill="black"/>
|
||||||
|
<path d="M19.25 31.625H27.5" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@ -77,6 +77,8 @@ ChatRootView {
|
|||||||
text: qsTr("Latest chat file name: %1").arg(root.chatFileName.length > 0 ? root.chatFileName : "Unsaved")
|
text: qsTr("Latest chat file name: %1").arg(root.chatFileName.length > 0 ? root.chatFileName : "Unsaved")
|
||||||
}
|
}
|
||||||
openChatHistory.onClicked: root.openChatHistoryFolder()
|
openChatHistory.onClicked: root.openChatHistoryFolder()
|
||||||
|
rulesButton.onClicked: rulesViewer.open()
|
||||||
|
activeRulesCount: root.activeRulesCount
|
||||||
pinButton {
|
pinButton {
|
||||||
visible: typeof _chatview !== 'undefined'
|
visible: typeof _chatview !== 'undefined'
|
||||||
checked: typeof _chatview !== 'undefined' ? _chatview.isPin : false
|
checked: typeof _chatview !== 'undefined' ? _chatview.isPin : false
|
||||||
@ -162,10 +164,10 @@ ChatRootView {
|
|||||||
toolContent: model.content
|
toolContent: model.content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: fileEditSuggestionComponent
|
id: fileEditSuggestionComponent
|
||||||
|
|
||||||
FileEditChangesItem {
|
FileEditChangesItem {
|
||||||
id: fileEditItem
|
id: fileEditItem
|
||||||
|
|
||||||
@ -332,6 +334,21 @@ ChatRootView {
|
|||||||
z: 1000
|
z: 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RulesViewer {
|
||||||
|
id: rulesViewer
|
||||||
|
|
||||||
|
width: parent.width * 0.8
|
||||||
|
height: parent.height * 0.8
|
||||||
|
x: (parent.width - width) / 2
|
||||||
|
y: (parent.height - height) / 2
|
||||||
|
|
||||||
|
activeRules: root.activeRules
|
||||||
|
ruleContentAreaText: root.getRuleContent(rulesViewer.rulesCurrentIndex)
|
||||||
|
|
||||||
|
onRefreshRules: root.refreshRules()
|
||||||
|
onOpenRulesFolder: root.openRulesFolder()
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root
|
target: root
|
||||||
function onLastErrorMessageChanged() {
|
function onLastErrorMessageChanged() {
|
||||||
|
|||||||
259
ChatView/qml/parts/RulesViewer.qml
Normal file
259
ChatView/qml/parts/RulesViewer.qml
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 Petr Mironychev
|
||||||
|
*
|
||||||
|
* This file is part of QodeAssist.
|
||||||
|
*
|
||||||
|
* QodeAssist is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* QodeAssist is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Controls.Basic as QQC
|
||||||
|
|
||||||
|
import UIControls
|
||||||
|
import ChatView
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var activeRules
|
||||||
|
|
||||||
|
property alias rulesCurrentIndex: rulesList.currentIndex
|
||||||
|
property alias ruleContentAreaText: ruleContentArea.text
|
||||||
|
|
||||||
|
signal refreshRules()
|
||||||
|
signal openRulesFolder()
|
||||||
|
|
||||||
|
modal: true
|
||||||
|
focus: true
|
||||||
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: palette.window
|
||||||
|
border.color: palette.mid
|
||||||
|
border.width: 1
|
||||||
|
radius: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatUtils {
|
||||||
|
id: utils
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 10
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("Active Project Rules")
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.bold: true
|
||||||
|
color: palette.text
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
text: qsTr("Open Folder")
|
||||||
|
onClicked: root.openRulesFolder()
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
text: qsTr("Refresh")
|
||||||
|
onClicked: root.refreshRules()
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
text: qsTr("Close")
|
||||||
|
onClicked: root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 1
|
||||||
|
color: palette.mid
|
||||||
|
}
|
||||||
|
|
||||||
|
SplitView {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
orientation: Qt.Horizontal
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
SplitView.minimumWidth: 200
|
||||||
|
SplitView.preferredWidth: parent.width * 0.3
|
||||||
|
color: palette.base
|
||||||
|
border.color: palette.mid
|
||||||
|
border.width: 1
|
||||||
|
radius: 2
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 5
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("Rules Files (%1)").arg(rulesList.count)
|
||||||
|
font.pixelSize: 12
|
||||||
|
font.bold: true
|
||||||
|
color: palette.text
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: rulesList
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
model: root.activeRules
|
||||||
|
currentIndex: 0
|
||||||
|
|
||||||
|
delegate: ItemDelegate {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
width: ListView.view.width
|
||||||
|
highlighted: ListView.isCurrentItem
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: {
|
||||||
|
if (parent.highlighted) {
|
||||||
|
return palette.highlight
|
||||||
|
} else if (parent.hovered) {
|
||||||
|
return Qt.tint(palette.base, Qt.rgba(0, 0, 0, 0.05))
|
||||||
|
}
|
||||||
|
return "transparent"
|
||||||
|
}
|
||||||
|
radius: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: modelData.fileName
|
||||||
|
font.pixelSize: 11
|
||||||
|
color: parent.parent.highlighted ? palette.highlightedText : palette.text
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("Category: %1").arg(modelData.category)
|
||||||
|
font.pixelSize: 9
|
||||||
|
color: parent.parent.highlighted ? palette.highlightedText : palette.mid
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
rulesList.currentIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: QQC.ScrollBar {
|
||||||
|
id: scroll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
visible: rulesList.count === 0
|
||||||
|
text: qsTr("No rules found.\nCreate .md files in:\n.qodeassist/rules/common/\n.qodeassist/rules/chat/")
|
||||||
|
font.pixelSize: 10
|
||||||
|
color: palette.mid
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.alignment: Qt.AlignCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
SplitView.fillWidth: true
|
||||||
|
color: palette.base
|
||||||
|
border.color: palette.mid
|
||||||
|
border.width: 1
|
||||||
|
radius: 2
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 5
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("Content")
|
||||||
|
font.pixelSize: 12
|
||||||
|
font.bold: true
|
||||||
|
color: palette.text
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
text: qsTr("Copy")
|
||||||
|
enabled: ruleContentArea.text.length > 0
|
||||||
|
onClicked: utils.copyToClipboard(ruleContentArea.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
QQC.TextArea {
|
||||||
|
id: ruleContentArea
|
||||||
|
|
||||||
|
readOnly: true
|
||||||
|
wrapMode: TextArea.Wrap
|
||||||
|
selectByMouse: true
|
||||||
|
color: palette.text
|
||||||
|
font.family: "monospace"
|
||||||
|
font.pixelSize: 11
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: Qt.darker(palette.base, 1.02)
|
||||||
|
border.color: palette.mid
|
||||||
|
border.width: 1
|
||||||
|
radius: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
placeholderText: qsTr("Select a rule file to view its content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("Rules are loaded from .qodeassist/rules/ directory in your project.\n" +
|
||||||
|
"Common rules apply to all contexts, chat rules apply only to chat assistant.")
|
||||||
|
font.pixelSize: 9
|
||||||
|
color: palette.mid
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -33,6 +33,8 @@ Rectangle {
|
|||||||
property alias recentPath: recentPathId
|
property alias recentPath: recentPathId
|
||||||
property alias openChatHistory: openChatHistoryId
|
property alias openChatHistory: openChatHistoryId
|
||||||
property alias pinButton: pinButtonId
|
property alias pinButton: pinButtonId
|
||||||
|
property alias rulesButton: rulesButtonId
|
||||||
|
property alias activeRulesCount: activeRulesCountId.text
|
||||||
|
|
||||||
color: palette.window.hslLightness > 0.5 ?
|
color: palette.window.hslLightness > 0.5 ?
|
||||||
Qt.darker(palette.window, 1.1) :
|
Qt.darker(palette.window, 1.1) :
|
||||||
@ -126,6 +128,38 @@ Rectangle {
|
|||||||
ToolTip.text: qsTr("Show in system")
|
ToolTip.text: qsTr("Show in system")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: rulesButtonId
|
||||||
|
|
||||||
|
icon {
|
||||||
|
source: "qrc:/qt/qml/ChatView/icons/rules-icon.svg"
|
||||||
|
height: 15
|
||||||
|
width: 15
|
||||||
|
}
|
||||||
|
text: " "
|
||||||
|
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.delay: 250
|
||||||
|
ToolTip.text: root.activeRulesCount > 0
|
||||||
|
? qsTr("View active project rules (%1)").arg(root.activeRulesCount)
|
||||||
|
: qsTr("View active project rules (no rules found)")
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: activeRulesCountId
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
bottom: parent.bottom
|
||||||
|
bottomMargin: 2
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
color: palette.text
|
||||||
|
font.pixelSize: 10
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,4 +109,73 @@ QString RulesLoader::getProjectPath(ProjectExplorer::Project *project)
|
|||||||
return project->projectDirectory().toUrlishString();
|
return project->projectDirectory().toUrlishString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVector<RuleFileInfo> RulesLoader::getRuleFiles(const QString &projectPath, RulesContext context)
|
||||||
|
{
|
||||||
|
if (projectPath.isEmpty()) {
|
||||||
|
return QVector<RuleFileInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<RuleFileInfo> result;
|
||||||
|
QString basePath = projectPath + "/.qodeassist/rules";
|
||||||
|
|
||||||
|
// Always include common rules
|
||||||
|
result.append(collectMarkdownFiles(basePath + "/common", "common"));
|
||||||
|
|
||||||
|
// Add context-specific rules
|
||||||
|
switch (context) {
|
||||||
|
case RulesContext::Completions:
|
||||||
|
result.append(collectMarkdownFiles(basePath + "/completions", "completions"));
|
||||||
|
break;
|
||||||
|
case RulesContext::Chat:
|
||||||
|
result.append(collectMarkdownFiles(basePath + "/chat", "chat"));
|
||||||
|
break;
|
||||||
|
case RulesContext::QuickRefactor:
|
||||||
|
result.append(collectMarkdownFiles(basePath + "/quickrefactor", "quickrefactor"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<RuleFileInfo> RulesLoader::getRuleFilesForProject(
|
||||||
|
ProjectExplorer::Project *project, RulesContext context)
|
||||||
|
{
|
||||||
|
if (!project) {
|
||||||
|
return QVector<RuleFileInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString projectPath = getProjectPath(project);
|
||||||
|
return getRuleFiles(projectPath, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString RulesLoader::loadRuleFileContent(const QString &filePath)
|
||||||
|
{
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.readAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<RuleFileInfo> RulesLoader::collectMarkdownFiles(
|
||||||
|
const QString &dirPath, const QString &category)
|
||||||
|
{
|
||||||
|
QVector<RuleFileInfo> result;
|
||||||
|
QDir dir(dirPath);
|
||||||
|
|
||||||
|
if (!dir.exists()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList mdFiles = dir.entryList({"*.md"}, QDir::Files, QDir::Name);
|
||||||
|
|
||||||
|
for (const QString &fileName : mdFiles) {
|
||||||
|
QString fullPath = dir.filePath(fileName);
|
||||||
|
result.append({fullPath, fileName, category});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::LLMCore
|
} // namespace QodeAssist::LLMCore
|
||||||
|
|||||||
@ -29,15 +29,28 @@ namespace QodeAssist::LLMCore {
|
|||||||
|
|
||||||
enum class RulesContext { Completions, Chat, QuickRefactor };
|
enum class RulesContext { Completions, Chat, QuickRefactor };
|
||||||
|
|
||||||
|
struct RuleFileInfo
|
||||||
|
{
|
||||||
|
QString filePath;
|
||||||
|
QString fileName;
|
||||||
|
QString category; // "common", "chat", "completions", "quickrefactor"
|
||||||
|
};
|
||||||
|
|
||||||
class RulesLoader
|
class RulesLoader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static QString loadRules(const QString &projectPath, RulesContext context);
|
static QString loadRules(const QString &projectPath, RulesContext context);
|
||||||
static QString loadRulesForProject(ProjectExplorer::Project *project, RulesContext context);
|
static QString loadRulesForProject(ProjectExplorer::Project *project, RulesContext context);
|
||||||
static ProjectExplorer::Project *getActiveProject();
|
static ProjectExplorer::Project *getActiveProject();
|
||||||
|
|
||||||
|
// New methods for getting rule files info
|
||||||
|
static QVector<RuleFileInfo> getRuleFiles(const QString &projectPath, RulesContext context);
|
||||||
|
static QVector<RuleFileInfo> getRuleFilesForProject(ProjectExplorer::Project *project, RulesContext context);
|
||||||
|
static QString loadRuleFileContent(const QString &filePath);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static QString loadAllMarkdownFiles(const QString &dirPath);
|
static QString loadAllMarkdownFiles(const QString &dirPath);
|
||||||
|
static QVector<RuleFileInfo> collectMarkdownFiles(const QString &dirPath, const QString &category);
|
||||||
static QString getProjectPath(ProjectExplorer::Project *project);
|
static QString getProjectPath(ProjectExplorer::Project *project);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user