mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-05-30 10:59:30 -04:00
feat: Add chat to editor view and refactor current openning
This commit is contained in:
@@ -75,6 +75,7 @@ qt_add_qml_module(QodeAssistChatView
|
||||
InputTokenCounter.hpp InputTokenCounter.cpp
|
||||
ChatHistoryStore.hpp ChatHistoryStore.cpp
|
||||
FileMentionItem.hpp FileMentionItem.cpp
|
||||
SessionFileRegistry.hpp SessionFileRegistry.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(QodeAssistChatView
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "ChatRootView.hpp"
|
||||
|
||||
#include <QAction>
|
||||
#include <QClipboard>
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
@@ -10,8 +11,12 @@
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QMessageBox>
|
||||
#include <QQmlContext>
|
||||
#include <QQmlEngine>
|
||||
#include <QTextStream>
|
||||
|
||||
#include <coreplugin/actionmanager/actionmanager.h>
|
||||
#include <coreplugin/actionmanager/command.h>
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectexplorer.h>
|
||||
@@ -19,6 +24,8 @@
|
||||
#include <utils/theme/theme.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
#include "QodeAssistConstants.hpp"
|
||||
|
||||
#include "AgentRoleController.hpp"
|
||||
#include "ChatAssistantSettings.hpp"
|
||||
#include "ChatConfigurationController.hpp"
|
||||
@@ -30,11 +37,20 @@
|
||||
#include "SettingsConstants.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "ProvidersManager.hpp"
|
||||
#include "SessionFileRegistry.hpp"
|
||||
#include "context/ContextManager.hpp"
|
||||
#include "pluginllmcore/RulesLoader.hpp"
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
namespace {
|
||||
bool isChatEditor(Core::IEditor *editor)
|
||||
{
|
||||
return editor && editor->document()
|
||||
&& editor->document()->id() == Utils::Id(Constants::QODE_ASSIST_CHAT_EDITOR_ID);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ChatRootView::ChatRootView(QQuickItem *parent)
|
||||
: QQuickItem(parent)
|
||||
, m_chatModel(new ChatModel(this))
|
||||
@@ -278,6 +294,25 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
||||
});
|
||||
}
|
||||
|
||||
ChatRootView::~ChatRootView()
|
||||
{
|
||||
if (m_sessionFileRegistry && !m_recentFilePath.isEmpty()) {
|
||||
m_sessionFileRegistry->release(m_recentFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
SessionFileRegistry *ChatRootView::sessionFileRegistry() const
|
||||
{
|
||||
if (!m_sessionFileRegistryResolved) {
|
||||
m_sessionFileRegistryResolved = true;
|
||||
if (auto context = qmlContext(this)) {
|
||||
m_sessionFileRegistry = qobject_cast<SessionFileRegistry *>(
|
||||
context->contextProperty("sessionFileRegistry").value<QObject *>());
|
||||
}
|
||||
}
|
||||
return m_sessionFileRegistry;
|
||||
}
|
||||
|
||||
ChatModel *ChatRootView::chatModel() const
|
||||
{
|
||||
return m_chatModel;
|
||||
@@ -341,6 +376,9 @@ void ChatRootView::dispatchSend(
|
||||
{
|
||||
if (m_recentFilePath.isEmpty()) {
|
||||
QString filePath = getAutosaveFilePath(message, attachments);
|
||||
if (auto registry = sessionFileRegistry()) {
|
||||
filePath = registry->uniqueFreePath(filePath);
|
||||
}
|
||||
if (!filePath.isEmpty()) {
|
||||
setRecentFilePath(filePath);
|
||||
LOG_MESSAGE(QString("Set chat file path for new chat: %1").arg(filePath));
|
||||
@@ -402,6 +440,15 @@ QString ChatRootView::currentTemplate() const
|
||||
|
||||
void ChatRootView::saveHistory(const QString &filePath)
|
||||
{
|
||||
if (filePath != m_recentFilePath) {
|
||||
if (auto registry = sessionFileRegistry(); registry && registry->isLocked(filePath)) {
|
||||
m_lastErrorMessage
|
||||
= tr("This chat file is already in use by another QodeAssist chat session.");
|
||||
emit lastErrorMessageChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto result = m_historyStore->save(filePath);
|
||||
if (!result.success) {
|
||||
LOG_MESSAGE(QString("Failed to save chat history: %1").arg(result.errorMessage));
|
||||
@@ -412,6 +459,15 @@ void ChatRootView::saveHistory(const QString &filePath)
|
||||
|
||||
void ChatRootView::loadHistory(const QString &filePath)
|
||||
{
|
||||
if (filePath != m_recentFilePath) {
|
||||
if (auto registry = sessionFileRegistry(); registry && registry->isLocked(filePath)) {
|
||||
m_lastErrorMessage
|
||||
= tr("This chat is already open in another QodeAssist chat session.");
|
||||
emit lastErrorMessageChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto result = m_historyStore->load(filePath);
|
||||
if (!result.success) {
|
||||
LOG_MESSAGE(QString("Failed to load chat history: %1").arg(result.errorMessage));
|
||||
@@ -446,11 +502,18 @@ void ChatRootView::autosave()
|
||||
return;
|
||||
}
|
||||
|
||||
QString filePath = getAutosaveFilePath();
|
||||
if (!filePath.isEmpty()) {
|
||||
m_historyStore->save(filePath);
|
||||
if (m_recentFilePath.isEmpty()) {
|
||||
QString filePath = getAutosaveFilePath();
|
||||
if (auto registry = sessionFileRegistry()) {
|
||||
filePath = registry->uniqueFreePath(filePath);
|
||||
}
|
||||
if (filePath.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
setRecentFilePath(filePath);
|
||||
}
|
||||
|
||||
m_historyStore->save(m_recentFilePath);
|
||||
}
|
||||
|
||||
QString ChatRootView::getAutosaveFilePath() const
|
||||
@@ -671,6 +734,76 @@ void ChatRootView::openFileInEditor(const QString &filePath)
|
||||
Core::EditorManager::openEditor(Utils::FilePath::fromString(filePath));
|
||||
}
|
||||
|
||||
void ChatRootView::triggerOpenChatCommand(Utils::Id commandId)
|
||||
{
|
||||
if (auto command = Core::ActionManager::command(commandId)) {
|
||||
if (auto action = command->action())
|
||||
action->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRootView::handOffSession()
|
||||
{
|
||||
if (m_chatModel->rowCount() > 0) {
|
||||
if (m_recentFilePath.isEmpty()) {
|
||||
QString filePath = getAutosaveFilePath();
|
||||
if (auto registry = sessionFileRegistry())
|
||||
filePath = registry->uniqueFreePath(filePath);
|
||||
if (!filePath.isEmpty())
|
||||
setRecentFilePath(filePath);
|
||||
}
|
||||
if (!m_recentFilePath.isEmpty())
|
||||
m_historyStore->save(m_recentFilePath);
|
||||
}
|
||||
|
||||
if (auto registry = sessionFileRegistry(); registry && !m_recentFilePath.isEmpty())
|
||||
registry->setPendingChatFile(m_recentFilePath);
|
||||
|
||||
setRecentFilePath(QString{});
|
||||
}
|
||||
|
||||
void ChatRootView::consumePendingChatFile()
|
||||
{
|
||||
if (auto registry = sessionFileRegistry()) {
|
||||
const QString pending = registry->takePendingChatFile();
|
||||
if (!pending.isEmpty())
|
||||
loadHistory(pending);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRootView::relocateToSplit()
|
||||
{
|
||||
handOffSession();
|
||||
triggerOpenChatCommand(Constants::QODE_ASSIST_SHOW_CHAT_ACTION);
|
||||
clearMessages();
|
||||
clearAttachmentFiles();
|
||||
emit closeHostRequested();
|
||||
}
|
||||
|
||||
void ChatRootView::relocateToWindow()
|
||||
{
|
||||
handOffSession();
|
||||
triggerOpenChatCommand(Constants::QODE_ASSIST_OPEN_CHAT_WINDOW_ACTION);
|
||||
clearMessages();
|
||||
clearAttachmentFiles();
|
||||
emit closeHostRequested();
|
||||
|
||||
// Closing the source split raises the main window; re-raise the chat window once that
|
||||
// queued teardown has run. The registry outlives this view, which the split close deletes.
|
||||
if (auto registry = sessionFileRegistry()) {
|
||||
QMetaObject::invokeMethod(
|
||||
registry,
|
||||
[] {
|
||||
if (auto command = Core::ActionManager::command(
|
||||
Constants::QODE_ASSIST_OPEN_CHAT_WINDOW_ACTION)) {
|
||||
if (auto action = command->action())
|
||||
action->trigger();
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRootView::updateInputTokensCount()
|
||||
{
|
||||
m_tokenCounter->recompute();
|
||||
@@ -688,6 +821,10 @@ bool ChatRootView::isSyncOpenFiles() const
|
||||
|
||||
void ChatRootView::onEditorAboutToClose(Core::IEditor *editor)
|
||||
{
|
||||
if (isChatEditor(editor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
||||
QString filePath = document->filePath().toFSPathString();
|
||||
m_linkedFiles.removeOne(filePath);
|
||||
@@ -703,6 +840,10 @@ void ChatRootView::onEditorAboutToClose(Core::IEditor *editor)
|
||||
|
||||
void ChatRootView::onAppendLinkFileFromEditor(Core::IEditor *editor)
|
||||
{
|
||||
if (isChatEditor(editor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
||||
QString filePath = document->filePath().toFSPathString();
|
||||
if (!m_linkedFiles.contains(filePath) && !shouldIgnoreFileForAttach(document->filePath())) {
|
||||
@@ -714,6 +855,10 @@ void ChatRootView::onAppendLinkFileFromEditor(Core::IEditor *editor)
|
||||
|
||||
void ChatRootView::onEditorCreated(Core::IEditor *editor, const Utils::FilePath &filePath)
|
||||
{
|
||||
if (isChatEditor(editor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (editor && editor->document()) {
|
||||
m_currentEditors.append(editor);
|
||||
emit openFilesChanged();
|
||||
@@ -732,12 +877,23 @@ QString ChatRootView::chatFilePath() const
|
||||
|
||||
void ChatRootView::setRecentFilePath(const QString &filePath)
|
||||
{
|
||||
if (m_recentFilePath != filePath) {
|
||||
m_recentFilePath = filePath;
|
||||
m_clientInterface->setChatFilePath(filePath);
|
||||
m_fileManager->setChatFilePath(filePath);
|
||||
emit chatFileNameChanged();
|
||||
if (m_recentFilePath == filePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto registry = sessionFileRegistry()) {
|
||||
if (!m_recentFilePath.isEmpty()) {
|
||||
registry->release(m_recentFilePath);
|
||||
}
|
||||
if (!filePath.isEmpty()) {
|
||||
registry->lock(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
m_recentFilePath = filePath;
|
||||
m_clientInterface->setChatFilePath(filePath);
|
||||
m_fileManager->setChatFilePath(filePath);
|
||||
emit chatFileNameChanged();
|
||||
}
|
||||
|
||||
bool ChatRootView::shouldIgnoreFileForAttach(const Utils::FilePath &filePath)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPointer>
|
||||
#include <QQuickItem>
|
||||
#include <QVariantList>
|
||||
|
||||
@@ -20,6 +21,7 @@ class ChatConfigurationController;
|
||||
class FileEditController;
|
||||
class InputTokenCounter;
|
||||
class ChatHistoryStore;
|
||||
class SessionFileRegistry;
|
||||
|
||||
class ChatRootView : public QQuickItem
|
||||
{
|
||||
@@ -62,6 +64,7 @@ class ChatRootView : public QQuickItem
|
||||
|
||||
public:
|
||||
ChatRootView(QQuickItem *parent = nullptr);
|
||||
~ChatRootView() override;
|
||||
|
||||
ChatModel *chatModel() const;
|
||||
QString currentTemplate() const;
|
||||
@@ -96,6 +99,11 @@ public:
|
||||
|
||||
Q_INVOKABLE void openFileInEditor(const QString &filePath);
|
||||
|
||||
Q_INVOKABLE void relocateToSplit();
|
||||
Q_INVOKABLE void relocateToWindow();
|
||||
|
||||
void consumePendingChatFile();
|
||||
|
||||
Q_INVOKABLE void updateInputTokensCount();
|
||||
int inputTokensCount() const;
|
||||
|
||||
@@ -216,7 +224,11 @@ signals:
|
||||
|
||||
void openFilesChanged();
|
||||
|
||||
void closeHostRequested();
|
||||
|
||||
private:
|
||||
void triggerOpenChatCommand(Utils::Id commandId);
|
||||
void handOffSession();
|
||||
bool deferSendForAutoCompress(
|
||||
const QString &message,
|
||||
const QStringList &attachments,
|
||||
@@ -231,6 +243,8 @@ private:
|
||||
bool useThinking);
|
||||
bool hasImageAttachments(const QStringList &attachments) const;
|
||||
|
||||
SessionFileRegistry *sessionFileRegistry() const;
|
||||
|
||||
ChatModel *m_chatModel;
|
||||
PluginLLMCore::PromptProviderChat m_promptProvider;
|
||||
ClientInterface *m_clientInterface;
|
||||
@@ -263,6 +277,8 @@ private:
|
||||
FileEditController *m_fileEditController;
|
||||
InputTokenCounter *m_tokenCounter;
|
||||
ChatHistoryStore *m_historyStore;
|
||||
mutable QPointer<SessionFileRegistry> m_sessionFileRegistry;
|
||||
mutable bool m_sessionFileRegistryResolved = false;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
#include <coreplugin/actionmanager/command.h>
|
||||
#include <logger/Logger.hpp>
|
||||
|
||||
#include "ChatRootView.hpp"
|
||||
#include "QodeAssistConstants.hpp"
|
||||
#include "SessionFileRegistry.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr Qt::WindowFlags baseFlags = Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint
|
||||
@@ -24,7 +26,7 @@ constexpr Qt::WindowFlags baseFlags = Qt::Window | Qt::WindowTitleHint | Qt::Win
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
ChatView::ChatView(QQmlEngine* engine)
|
||||
ChatView::ChatView(QQmlEngine *engine, SessionFileRegistry *sessionFileRegistry)
|
||||
: QQuickView{engine, nullptr}
|
||||
, m_isPin(false)
|
||||
{
|
||||
@@ -33,12 +35,23 @@ ChatView::ChatView(QQmlEngine* engine)
|
||||
{
|
||||
auto context = new QQmlContext{engine, this};
|
||||
context->setContextProperty("_chatview", this);
|
||||
context->setContextProperty("sessionFileRegistry", sessionFileRegistry);
|
||||
|
||||
auto component = new QQmlComponent{engine, QUrl{"qrc:/qt/qml/ChatView/qml/RootItem.qml"}, this};
|
||||
auto rootItem = component->create(context);
|
||||
|
||||
setContent(component->url(), component, rootItem);
|
||||
}
|
||||
|
||||
if (auto rootView = qobject_cast<ChatRootView *>(rootObject())) {
|
||||
connect(
|
||||
rootView,
|
||||
&ChatRootView::closeHostRequested,
|
||||
this,
|
||||
&QWindow::close,
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
setResizeMode(QQuickView::SizeRootObjectToView);
|
||||
setMinimumSize({400, 300});
|
||||
setFlags(baseFlags);
|
||||
|
||||
@@ -12,12 +12,14 @@
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
class SessionFileRegistry;
|
||||
|
||||
class ChatView : public QQuickView
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool isPin READ isPin WRITE setIsPin NOTIFY isPinChanged FINAL)
|
||||
public:
|
||||
ChatView(QQmlEngine* engine);
|
||||
ChatView(QQmlEngine *engine, SessionFileRegistry *sessionFileRegistry);
|
||||
|
||||
bool isPin() const;
|
||||
void setIsPin(bool newIsPin);
|
||||
|
||||
@@ -12,15 +12,17 @@
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include "QodeAssistConstants.hpp"
|
||||
#include "SessionFileRegistry.hpp"
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
ChatWidget::ChatWidget(QQmlEngine* engine, QWidget *parent)
|
||||
ChatWidget::ChatWidget(QQmlEngine *engine, SessionFileRegistry *sessionFileRegistry, QWidget *parent)
|
||||
: QQuickWidget{engine, parent}
|
||||
{
|
||||
/// @note setup quick view content
|
||||
{
|
||||
auto context = new QQmlContext{engine, this};
|
||||
context->setContextProperty("sessionFileRegistry", sessionFileRegistry);
|
||||
auto component = new QQmlComponent{engine, QUrl{"qrc:/qt/qml/ChatView/qml/RootItem.qml"}, this};
|
||||
auto rootItem = component->create(context);
|
||||
|
||||
|
||||
@@ -7,12 +7,17 @@
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
class SessionFileRegistry;
|
||||
|
||||
class ChatWidget : public QQuickWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ChatWidget(QQmlEngine* engine, QWidget *parent = nullptr);
|
||||
explicit ChatWidget(
|
||||
QQmlEngine *engine,
|
||||
SessionFileRegistry *sessionFileRegistry,
|
||||
QWidget *parent = nullptr);
|
||||
~ChatWidget() = default;
|
||||
|
||||
Q_INVOKABLE void clear();
|
||||
|
||||
67
ChatView/SessionFileRegistry.cpp
Normal file
67
ChatView/SessionFileRegistry.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "SessionFileRegistry.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
SessionFileRegistry::SessionFileRegistry(QObject *parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
bool SessionFileRegistry::isLocked(const QString &path) const
|
||||
{
|
||||
return !path.isEmpty() && m_lockedPaths.contains(path);
|
||||
}
|
||||
|
||||
bool SessionFileRegistry::lock(const QString &path)
|
||||
{
|
||||
if (path.isEmpty() || m_lockedPaths.contains(path)) {
|
||||
return false;
|
||||
}
|
||||
m_lockedPaths.insert(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SessionFileRegistry::release(const QString &path)
|
||||
{
|
||||
m_lockedPaths.remove(path);
|
||||
}
|
||||
|
||||
void SessionFileRegistry::setPendingChatFile(const QString &path)
|
||||
{
|
||||
m_pendingChatFile = path;
|
||||
}
|
||||
|
||||
QString SessionFileRegistry::takePendingChatFile()
|
||||
{
|
||||
return std::exchange(m_pendingChatFile, QString{});
|
||||
}
|
||||
|
||||
QString SessionFileRegistry::uniqueFreePath(const QString &desiredPath) const
|
||||
{
|
||||
if (desiredPath.isEmpty() || !m_lockedPaths.contains(desiredPath)) {
|
||||
return desiredPath;
|
||||
}
|
||||
|
||||
const QFileInfo info(desiredPath);
|
||||
const QString dir = info.path();
|
||||
const QString base = info.completeBaseName();
|
||||
const QString suffix = info.suffix();
|
||||
|
||||
for (int counter = 2;; ++counter) {
|
||||
QString candidate = dir + '/' + base + '_' + QString::number(counter);
|
||||
if (!suffix.isEmpty()) {
|
||||
candidate += '.' + suffix;
|
||||
}
|
||||
if (!m_lockedPaths.contains(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
38
ChatView/SessionFileRegistry.hpp
Normal file
38
ChatView/SessionFileRegistry.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
// Shared registry of chat session (autosave) file paths that are currently held by a live
|
||||
// chat instance. Lets every chat view — bottom pane, navigation panel, editor split — claim
|
||||
// a unique history file so two sessions never autosave into the same path.
|
||||
class SessionFileRegistry : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SessionFileRegistry(QObject *parent = nullptr);
|
||||
|
||||
bool isLocked(const QString &path) const;
|
||||
bool lock(const QString &path);
|
||||
void release(const QString &path);
|
||||
|
||||
QString uniqueFreePath(const QString &desiredPath) const;
|
||||
|
||||
// Handoff slot for relocating a live chat between hosts (split <-> window): the source
|
||||
// chat stores its history file here, the freshly created host picks it up exactly once.
|
||||
void setPendingChatFile(const QString &path);
|
||||
QString takePendingChatFile();
|
||||
|
||||
private:
|
||||
QSet<QString> m_lockedPaths;
|
||||
QString m_pendingChatFile;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
@@ -116,6 +116,17 @@ ChatRootView {
|
||||
checked: typeof _chatview !== 'undefined' ? _chatview.isPin : false
|
||||
onCheckedChanged: _chatview.isPin = topBar.pinButton.checked
|
||||
}
|
||||
relocateButton {
|
||||
ToolTip.text: (typeof _chatview !== 'undefined')
|
||||
? qsTr("Move this chat to an editor split")
|
||||
: qsTr("Move this chat to a separate window")
|
||||
onClicked: {
|
||||
if (typeof _chatview !== 'undefined')
|
||||
root.relocateToSplit()
|
||||
else
|
||||
root.relocateToWindow()
|
||||
}
|
||||
}
|
||||
toolsButton {
|
||||
checked: root.useTools
|
||||
onCheckedChanged: {
|
||||
|
||||
@@ -17,6 +17,7 @@ Rectangle {
|
||||
property alias recentPath: recentPathId
|
||||
property alias openChatHistory: openChatHistoryId
|
||||
property alias pinButton: pinButtonId
|
||||
property alias relocateButton: relocateButtonId
|
||||
property alias contextButton: contextButtonId
|
||||
property alias toolsButton: toolsButtonId
|
||||
property alias thinkingMode: thinkingModeId
|
||||
@@ -61,6 +62,21 @@ Rectangle {
|
||||
: qsTr("Pin chat window to the top")
|
||||
}
|
||||
|
||||
QoAButton {
|
||||
id: relocateButtonId
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
icon {
|
||||
source: "qrc:/qt/qml/ChatView/icons/open-in-editor.svg"
|
||||
color: palette.window.hslLightness > 0.5 ? "#000000" : "#FFFFFF"
|
||||
height: 15
|
||||
width: 15
|
||||
}
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.delay: 250
|
||||
}
|
||||
|
||||
QoAComboBox {
|
||||
id: configSelectorId
|
||||
|
||||
|
||||
Reference in New Issue
Block a user