mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-06-12 17:29:13 -04:00
fix: Change getting focus to chat in editor and name of title
This commit is contained in:
@@ -44,9 +44,11 @@ qt_add_qml_module(QodeAssistChatView
|
||||
icons/window-unlock.svg
|
||||
icons/chat-icon.svg
|
||||
icons/chat-pause-icon.svg
|
||||
icons/new-chat-icon.svg
|
||||
icons/rules-icon.svg
|
||||
icons/context-icon.svg
|
||||
icons/open-in-editor.svg
|
||||
icons/open-in-window.svg
|
||||
icons/apply-changes-button.svg
|
||||
icons/undo-changes-button.svg
|
||||
icons/reject-changes-button.svg
|
||||
|
||||
@@ -112,6 +112,18 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
||||
setRecentFilePath(QString{});
|
||||
m_fileEditController->clearCurrentRequestId();
|
||||
});
|
||||
auto maybeEmitTitle = [this] {
|
||||
const QString newTitle = computeChatTitle();
|
||||
if (newTitle == m_cachedChatTitle)
|
||||
return;
|
||||
m_cachedChatTitle = newTitle;
|
||||
emit chatTitleChanged();
|
||||
};
|
||||
connect(m_chatModel, &ChatModel::modelReseted, this, maybeEmitTitle);
|
||||
connect(m_chatModel, &QAbstractItemModel::modelReset, this, maybeEmitTitle);
|
||||
connect(m_chatModel, &QAbstractItemModel::rowsInserted, this, maybeEmitTitle);
|
||||
connect(m_chatModel, &QAbstractItemModel::rowsRemoved, this, maybeEmitTitle);
|
||||
connect(m_chatModel, &QAbstractItemModel::dataChanged, this, maybeEmitTitle);
|
||||
connect(this, &ChatRootView::attachmentFilesChanged, this, [this]() {
|
||||
m_tokenCounter->setAttachments(m_attachmentFiles);
|
||||
});
|
||||
@@ -792,6 +804,51 @@ void ChatRootView::triggerOpenChatCommand(Utils::Id commandId)
|
||||
}
|
||||
}
|
||||
|
||||
bool ChatRootView::isInEditor() const
|
||||
{
|
||||
return m_isInEditor;
|
||||
}
|
||||
|
||||
void ChatRootView::setInEditor(bool value)
|
||||
{
|
||||
if (m_isInEditor == value)
|
||||
return;
|
||||
m_isInEditor = value;
|
||||
emit isInEditorChanged();
|
||||
}
|
||||
|
||||
void ChatRootView::requestNewChat()
|
||||
{
|
||||
triggerOpenChatCommand(Constants::QODE_ASSIST_NEW_CHAT_ACTION);
|
||||
}
|
||||
|
||||
QString ChatRootView::chatTitle() const
|
||||
{
|
||||
if (m_cachedChatTitle.isEmpty())
|
||||
m_cachedChatTitle = computeChatTitle();
|
||||
return m_cachedChatTitle;
|
||||
}
|
||||
|
||||
QString ChatRootView::computeChatTitle() const
|
||||
{
|
||||
if (!m_chatModel)
|
||||
return {};
|
||||
const auto history = m_chatModel->getChatHistory();
|
||||
for (const auto &msg : history) {
|
||||
if (msg.role != ChatModel::User)
|
||||
continue;
|
||||
const QString content = msg.content.trimmed();
|
||||
if (content.isEmpty())
|
||||
continue;
|
||||
const QString firstLine = content.section(QChar('\n'), 0, 0).trimmed();
|
||||
constexpr int maxLen = 60;
|
||||
if (firstLine.length() > maxLen)
|
||||
return firstLine.left(maxLen - 1) + QChar(0x2026);
|
||||
return firstLine;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void ChatRootView::handOffSession()
|
||||
{
|
||||
if (m_chatModel->rowCount() > 0) {
|
||||
@@ -824,7 +881,7 @@ void ChatRootView::consumePendingChatFile()
|
||||
void ChatRootView::relocateToSplit()
|
||||
{
|
||||
handOffSession();
|
||||
triggerOpenChatCommand(Constants::QODE_ASSIST_SHOW_CHAT_ACTION);
|
||||
triggerOpenChatCommand(Constants::QODE_ASSIST_NEW_CHAT_ACTION);
|
||||
clearMessages();
|
||||
clearAttachmentFiles();
|
||||
emit closeHostRequested();
|
||||
|
||||
@@ -63,6 +63,8 @@ class ChatRootView : public QQuickItem
|
||||
Q_PROPERTY(QString currentAgentRoleDescription READ currentAgentRoleDescription NOTIFY currentAgentRoleChanged FINAL)
|
||||
Q_PROPERTY(QString currentAgentRoleSystemPrompt READ currentAgentRoleSystemPrompt NOTIFY currentAgentRoleChanged FINAL)
|
||||
Q_PROPERTY(bool isCompressing READ isCompressing NOTIFY isCompressingChanged FINAL)
|
||||
Q_PROPERTY(bool isInEditor READ isInEditor NOTIFY isInEditorChanged FINAL)
|
||||
Q_PROPERTY(QString chatTitle READ chatTitle NOTIFY chatTitleChanged FINAL)
|
||||
|
||||
QML_ELEMENT
|
||||
|
||||
@@ -183,6 +185,13 @@ public:
|
||||
|
||||
bool isCompressing() const;
|
||||
|
||||
bool isInEditor() const;
|
||||
void setInEditor(bool value);
|
||||
|
||||
QString chatTitle() const;
|
||||
|
||||
Q_INVOKABLE void requestNewChat();
|
||||
|
||||
public slots:
|
||||
void sendMessage(const QString &message);
|
||||
void copyToClipboard(const QString &text);
|
||||
@@ -228,11 +237,15 @@ signals:
|
||||
void compressionCompleted(const QString &compressedChatPath);
|
||||
void compressionFailed(const QString &error);
|
||||
|
||||
void isInEditorChanged();
|
||||
void chatTitleChanged();
|
||||
|
||||
void openFilesChanged();
|
||||
|
||||
void closeHostRequested();
|
||||
|
||||
private:
|
||||
QString computeChatTitle() const;
|
||||
void triggerOpenChatCommand(Utils::Id commandId);
|
||||
void handOffSession();
|
||||
bool deferSendForAutoCompress(
|
||||
@@ -271,6 +284,8 @@ private:
|
||||
};
|
||||
PendingSend m_pendingSend;
|
||||
bool m_isSyncOpenFiles;
|
||||
bool m_isInEditor = false;
|
||||
mutable QString m_cachedChatTitle;
|
||||
QList<Core::IEditor *> m_currentEditors;
|
||||
bool m_isRequestInProgress;
|
||||
QString m_lastErrorMessage;
|
||||
|
||||
@@ -21,6 +21,7 @@ ChatWidget::ChatWidget(
|
||||
QQmlEngine *engine,
|
||||
SessionFileRegistry *sessionFileRegistry,
|
||||
Skills::SkillsManager *skillsManager,
|
||||
bool registerOwnContext,
|
||||
QWidget *parent)
|
||||
: QQuickWidget{engine, parent}
|
||||
{
|
||||
@@ -37,10 +38,19 @@ ChatWidget::ChatWidget(
|
||||
setResizeMode(QQuickWidget::SizeRootObjectToView);
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
|
||||
auto ideContext = new Core::IContext{this};
|
||||
ideContext->setWidget(this);
|
||||
ideContext->setContext(Core::Context{Constants::QODE_ASSIST_CHAT_CONTEXT});
|
||||
Core::ICore::addContextObject(ideContext);
|
||||
if (registerOwnContext) {
|
||||
auto ideContext = new Core::IContext{this};
|
||||
ideContext->setWidget(this);
|
||||
ideContext->setContext(Core::Context{Constants::QODE_ASSIST_CHAT_CONTEXT});
|
||||
Core::ICore::addContextObject(ideContext);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatWidget::focusInEvent(QFocusEvent *event)
|
||||
{
|
||||
QQuickWidget::focusInEvent(event);
|
||||
if (rootObject())
|
||||
QMetaObject::invokeMethod(rootObject(), "focusInput");
|
||||
}
|
||||
|
||||
void ChatWidget::clear()
|
||||
|
||||
@@ -22,6 +22,7 @@ public:
|
||||
QQmlEngine *engine,
|
||||
SessionFileRegistry *sessionFileRegistry,
|
||||
Skills::SkillsManager *skillsManager,
|
||||
bool registerOwnContext = true,
|
||||
QWidget *parent = nullptr);
|
||||
~ChatWidget() = default;
|
||||
|
||||
@@ -38,6 +39,9 @@ public:
|
||||
|
||||
signals:
|
||||
void clearPressed();
|
||||
|
||||
protected:
|
||||
void focusInEvent(QFocusEvent *event) override;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
|
||||
5
ChatView/icons/new-chat-icon.svg
Normal file
5
ChatView/icons/new-chat-icon.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.6 0H2.4C1.08 0 0 1.08 0 2.4V16.8C0 18.12 1.08 19.2 2.4 19.2H7.2V22.8C7.2 23.46 7.74 24 8.4 24H9C9.3 24 9.6 23.88 9.84 23.652L14.28 19.2H21.6C22.92 19.2 24 18.12 24 16.8V2.4C24 1.08 22.92 0 21.6 0ZM21.6 16.8H13.44L8.76 21.48L8.4 21.6V16.8H2.4V2.4H21.6V16.8Z" fill="black"/>
|
||||
<rect x="11" y="5" width="2" height="9" rx="0.5" fill="black"/>
|
||||
<rect x="7.5" y="8.5" width="9" height="2" rx="0.5" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 526 B |
@@ -1,17 +1,6 @@
|
||||
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_74_52)">
|
||||
<mask id="mask0_74_52" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="44" height="44">
|
||||
<path d="M44 0H0V44H44V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_74_52)">
|
||||
<path d="M18 31C25.1797 31 31 25.1797 31 18C31 10.8203 25.1797 5 18 5C10.8203 5 5 10.8203 5 18C5 25.1797 10.8203 31 18 31Z" stroke="black" stroke-width="3.5"/>
|
||||
<path d="M27 27L38 38" stroke="black" stroke-width="3.5" stroke-linecap="round"/>
|
||||
<path d="M16.375 23L18.2841 11.3636H20.1023L18.1932 23H16.375ZM11.1648 20.1136L11.4659 18.2955H20.5568L20.2557 20.1136H11.1648ZM12.2841 23L14.1932 11.3636H16.0114L14.1023 23H12.2841ZM11.8295 16.0682L12.1364 14.25H21.2273L20.9205 16.0682H11.8295Z" fill="black"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_74_52">
|
||||
<rect width="44" height="44" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<svg width="44" height="44" viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(10 8) skewX(-15)" stroke="black" stroke-width="2" stroke-linejoin="round">
|
||||
<rect x="10" y="0" width="22" height="15" rx="3" ry="3" fill="black"/>
|
||||
<rect x="0" y="12" width="22" height="15" rx="3" ry="3" fill="none"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 943 B After Width: | Height: | Size: 348 B |
6
ChatView/icons/open-in-window.svg
Normal file
6
ChatView/icons/open-in-window.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="44" height="44" viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(10 8) skewX(-15)" stroke="black" stroke-width="2" stroke-linejoin="round">
|
||||
<rect x="10" y="0" width="22" height="15" rx="3" ry="3" fill="none"/>
|
||||
<rect x="0" y="12" width="22" height="15" rx="3" ry="3" fill="black"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 348 B |
@@ -87,21 +87,25 @@ ChatRootView {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: childrenRect.height + 10
|
||||
|
||||
isInEditor: root.isInEditor
|
||||
saveButton.onClicked: root.showSaveDialog()
|
||||
loadButton.onClicked: root.showLoadDialog()
|
||||
clearButton.onClicked: root.clearChat()
|
||||
newChatButton.onClicked: root.requestNewChat()
|
||||
tokensBadge {
|
||||
readonly property int sessionCached: root.chatModel.sessionCachedPromptTokens
|
||||
readonly property int sessionPrompt: root.chatModel.sessionPromptTokens || 0
|
||||
readonly property int sessionCompletion: root.chatModel.sessionCompletionTokens || 0
|
||||
readonly property int sessionCached: root.chatModel.sessionCachedPromptTokens || 0
|
||||
text: sessionCached > 0
|
||||
? qsTr("next ~%1 · session ↑%2 ↓%3 ↻%4")
|
||||
.arg(root.inputTokensCount)
|
||||
.arg(root.chatModel.sessionPromptTokens)
|
||||
.arg(root.chatModel.sessionCompletionTokens)
|
||||
.arg(sessionPrompt)
|
||||
.arg(sessionCompletion)
|
||||
.arg(sessionCached)
|
||||
: qsTr("next ~%1 · session ↑%2 ↓%3")
|
||||
.arg(root.inputTokensCount)
|
||||
.arg(root.chatModel.sessionPromptTokens)
|
||||
.arg(root.chatModel.sessionCompletionTokens)
|
||||
.arg(sessionPrompt)
|
||||
.arg(sessionCompletion)
|
||||
ToolTip.text: sessionCached > 0
|
||||
? qsTr("next request (estimate) · session prompt ↑ / completion ↓ / cached ↻ (provider cache hits)")
|
||||
: qsTr("next request (estimate) · session prompt ↑ / completion ↓")
|
||||
@@ -117,8 +121,11 @@ ChatRootView {
|
||||
onCheckedChanged: _chatview.isPin = topBar.pinButton.checked
|
||||
}
|
||||
relocateButton {
|
||||
icon.source: (typeof _chatview !== 'undefined')
|
||||
? "qrc:/qt/qml/ChatView/icons/open-in-editor.svg"
|
||||
: "qrc:/qt/qml/ChatView/icons/open-in-window.svg"
|
||||
ToolTip.text: (typeof _chatview !== 'undefined')
|
||||
? qsTr("Move this chat to an editor split")
|
||||
? qsTr("Move this chat to an editor tab")
|
||||
: qsTr("Move this chat to a separate window")
|
||||
onClicked: {
|
||||
if (typeof _chatview !== 'undefined')
|
||||
|
||||
@@ -10,9 +10,12 @@ import UIControls
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool isInEditor: false
|
||||
|
||||
property alias saveButton: saveButtonId
|
||||
property alias loadButton: loadButtonId
|
||||
property alias clearButton: clearButtonId
|
||||
property alias newChatButton: newChatButtonId
|
||||
property alias tokensBadge: tokensBadgeId
|
||||
property alias recentPath: recentPathId
|
||||
property alias openChatHistory: openChatHistoryId
|
||||
@@ -77,6 +80,43 @@ Rectangle {
|
||||
ToolTip.delay: 250
|
||||
}
|
||||
|
||||
QoASeparator {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
QoAButton {
|
||||
id: clearButtonId
|
||||
|
||||
icon {
|
||||
source: "qrc:/qt/qml/ChatView/icons/clean-icon-dark.svg"
|
||||
height: 15
|
||||
width: 8
|
||||
}
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.delay: 250
|
||||
ToolTip.text: qsTr("Clean chat")
|
||||
}
|
||||
|
||||
QoASeparator {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
QoAButton {
|
||||
id: newChatButtonId
|
||||
|
||||
visible: root.isInEditor
|
||||
|
||||
icon {
|
||||
source: "qrc:/qt/qml/ChatView/icons/new-chat-icon.svg"
|
||||
color: palette.window.hslLightness > 0.5 ? "#000000" : "#FFFFFF"
|
||||
height: 15
|
||||
width: 15
|
||||
}
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.delay: 250
|
||||
ToolTip.text: qsTr("Open new chat in a new tab")
|
||||
}
|
||||
|
||||
QoAComboBox {
|
||||
id: configSelectorId
|
||||
|
||||
@@ -276,21 +316,6 @@ Rectangle {
|
||||
ToolTip.delay: 250
|
||||
ToolTip.text: qsTr("Current amount tokens in chat and LLM limit threshold")
|
||||
}
|
||||
|
||||
QoASeparator {}
|
||||
|
||||
QoAButton {
|
||||
id: clearButtonId
|
||||
|
||||
icon {
|
||||
source: "qrc:/qt/qml/ChatView/icons/clean-icon-dark.svg"
|
||||
height: 15
|
||||
width: 8
|
||||
}
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.delay: 250
|
||||
ToolTip.text: qsTr("Clean chat")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user