gui: add bookmark feature for chat blocks
- Add bookmark button (📌) on chat block hover
- Bookmarks section in left panel (SessionTree) with grouped view
- Non-persistent GUI-only state using ListModel
- Click bookmark to scroll to that block in chat
- Bookmark format: sessionName/agentName with block content preview
- Add activeSessionName/activeAgentName C++ getters for display
This commit is contained in:
parent
c8897b8251
commit
96b6406871
|
|
@ -17,6 +17,16 @@ Column {
|
||||||
required property string blockName
|
required property string blockName
|
||||||
required property var blockSegments
|
required property var blockSegments
|
||||||
required property string blockRenderedHtml
|
required property string blockRenderedHtml
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
// Block index for bookmarking
|
||||||
|
property int blockIndex: index
|
||||||
|
|
||||||
|
// Bookmark functions (connected from parent)
|
||||||
|
property var addBookmark: function(sid, sn, aid, an, bi, bt, bn, p) { return false }
|
||||||
|
property var removeBookmark: function(sid, aid, bi) { return false }
|
||||||
|
property var checkBookmarked: function(sid, aid, bi) { return false }
|
||||||
|
property bool isBookmarked: checkBookmarked(ollie.activeSessionId, ollie.activeAgentId, blockIndex)
|
||||||
|
|
||||||
// Expand state based on block type
|
// Expand state based on block type
|
||||||
property bool blockExpanded: {
|
property bool blockExpanded: {
|
||||||
|
|
@ -179,6 +189,35 @@ Column {
|
||||||
if (content) clipboard.copyText(content)
|
if (content) clipboard.copyText(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bookmark
|
||||||
|
ToolButton {
|
||||||
|
text: root.isBookmarked ? "🔖" : "📌"
|
||||||
|
font.pixelSize: 12
|
||||||
|
implicitWidth: 24
|
||||||
|
implicitHeight: 22
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: root.isBookmarked ? "Remove bookmark" : "Bookmark"
|
||||||
|
onClicked: {
|
||||||
|
if (root.isBookmarked) {
|
||||||
|
root.removeBookmark(ollie.activeSessionId, ollie.activeAgentId, root.blockIndex)
|
||||||
|
} else {
|
||||||
|
var sessionName = ollie.activeSessionName || ollie.activeSessionId
|
||||||
|
var agentName = ollie.activeAgentName || ollie.activeAgentId
|
||||||
|
var preview = getBlockPlainText() || blockName
|
||||||
|
root.addBookmark(
|
||||||
|
ollie.activeSessionId,
|
||||||
|
sessionName,
|
||||||
|
ollie.activeAgentId,
|
||||||
|
agentName,
|
||||||
|
root.blockIndex,
|
||||||
|
blockType,
|
||||||
|
blockName,
|
||||||
|
preview
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,11 @@ Item {
|
||||||
function scrollToBottom() {
|
function scrollToBottom() {
|
||||||
chatView.positionViewAtEnd()
|
chatView.positionViewAtEnd()
|
||||||
}
|
}
|
||||||
|
function scrollToBlock(blockIndex) {
|
||||||
|
if (blockIndex >= 0 && blockIndex < chatModel.count) {
|
||||||
|
chatView.positionViewAtIndex(blockIndex, ListView.Center)
|
||||||
|
}
|
||||||
|
}
|
||||||
function trimVisibleBlocks() {
|
function trimVisibleBlocks() {
|
||||||
if (chatModel.count > chatPane.maxVisibleBlocks)
|
if (chatModel.count > chatPane.maxVisibleBlocks)
|
||||||
chatModel.trimFront(chatPane.maxVisibleBlocks)
|
chatModel.trimFront(chatPane.maxVisibleBlocks)
|
||||||
|
|
@ -265,6 +270,9 @@ Item {
|
||||||
|
|
||||||
delegate: ChatBlockDelegate {
|
delegate: ChatBlockDelegate {
|
||||||
width: chatView.width - 8
|
width: chatView.width - 8
|
||||||
|
addBookmark: root.addBookmark
|
||||||
|
removeBookmark: root.removeBookmark
|
||||||
|
checkBookmarked: root.isBookmarked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ Item {
|
||||||
id: sessionList
|
id: sessionList
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
Layout.minimumHeight: 100
|
||||||
model: sessionModel
|
model: sessionModel
|
||||||
clip: true
|
clip: true
|
||||||
focus: false
|
focus: false
|
||||||
|
|
@ -366,6 +367,102 @@ Item {
|
||||||
highlighted: (type === "agent" && agentId === ollie.activeAgentId && parentId === ollie.activeSessionId)
|
highlighted: (type === "agent" && agentId === ollie.activeAgentId && parentId === ollie.activeSessionId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Bookmarks Section ---
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 1
|
||||||
|
color: theme.midColor
|
||||||
|
visible: bookmarksSection.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: bookmarksSection
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: bookmarksExpanded ? Math.min(150, bookmarksList.contentHeight + 30) : 30
|
||||||
|
visible: bookmarksModel.count > 0
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
property bool bookmarksExpanded: true
|
||||||
|
|
||||||
|
ToolBar {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
ToolButton {
|
||||||
|
text: bookmarksSection.bookmarksExpanded ? "▼" : "▶"
|
||||||
|
implicitWidth: 24
|
||||||
|
onClicked: bookmarksSection.bookmarksExpanded = !bookmarksSection.bookmarksExpanded
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: "Bookmarks (" + bookmarksModel.count + ")"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
ToolButton {
|
||||||
|
text: "✕"
|
||||||
|
implicitWidth: 24
|
||||||
|
onClicked: bookmarksModel.clear()
|
||||||
|
ToolTip.text: "Clear all bookmarks"
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: bookmarksList
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
model: bookmarksModel
|
||||||
|
clip: true
|
||||||
|
visible: bookmarksSection.bookmarksExpanded
|
||||||
|
|
||||||
|
delegate: ItemDelegate {
|
||||||
|
width: bookmarksList.width
|
||||||
|
height: 36
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 8
|
||||||
|
anchors.rightMargin: 4
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: sessionName + "/" + agentName
|
||||||
|
font.pixelSize: theme.fontSize > 0 ? theme.fontSize * 0.75 : 9
|
||||||
|
color: theme.midColor
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: "[" + blockType + "] " + preview
|
||||||
|
font.pixelSize: theme.fontSize > 0 ? theme.fontSize * 0.85 : 11
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
text: "✕"
|
||||||
|
implicitWidth: 20
|
||||||
|
implicitHeight: 20
|
||||||
|
onClicked: root.removeBookmark(sessionId, agentId, blockIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
// Switch to the session/agent and scroll to block
|
||||||
|
ollie.switchAgent(sessionId, agentId)
|
||||||
|
// Signal to ChatPane to scroll to blockIndex
|
||||||
|
chatPane.scrollToBlock(blockIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function startRename(index) {
|
function startRename(index) {
|
||||||
var item = sessionModel.get(index)
|
var item = sessionModel.get(index)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,54 @@ ApplicationWindow {
|
||||||
font.family: theme.fontFamily || Qt.application.font.family
|
font.family: theme.fontFamily || Qt.application.font.family
|
||||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
|
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
|
||||||
|
|
||||||
|
// Bookmarks model (non-persistent, GUI state only)
|
||||||
|
// Each entry: { sessionId, sessionName, agentId, agentName, blockIndex, blockType, blockName, preview }
|
||||||
|
ListModel {
|
||||||
|
id: bookmarksModel
|
||||||
|
}
|
||||||
|
|
||||||
|
function addBookmark(sessionId, sessionName, agentId, agentName, blockIndex, blockType, blockName, preview) {
|
||||||
|
// Check for duplicate
|
||||||
|
for (var i = 0; i < bookmarksModel.count; i++) {
|
||||||
|
var b = bookmarksModel.get(i)
|
||||||
|
if (b.sessionId === sessionId && b.agentId === agentId && b.blockIndex === blockIndex) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bookmarksModel.append({
|
||||||
|
sessionId: sessionId,
|
||||||
|
sessionName: sessionName,
|
||||||
|
agentId: agentId,
|
||||||
|
agentName: agentName,
|
||||||
|
blockIndex: blockIndex,
|
||||||
|
blockType: blockType,
|
||||||
|
blockName: blockName,
|
||||||
|
preview: preview.substring(0, 80) + (preview.length > 80 ? "…" : "")
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeBookmark(sessionId, agentId, blockIndex) {
|
||||||
|
for (var i = 0; i < bookmarksModel.count; i++) {
|
||||||
|
var b = bookmarksModel.get(i)
|
||||||
|
if (b.sessionId === sessionId && b.agentId === agentId && b.blockIndex === blockIndex) {
|
||||||
|
bookmarksModel.remove(i)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBookmarked(sessionId, agentId, blockIndex) {
|
||||||
|
for (var i = 0; i < bookmarksModel.count; i++) {
|
||||||
|
var b = bookmarksModel.get(i)
|
||||||
|
if (b.sessionId === sessionId && b.agentId === agentId && b.blockIndex === blockIndex) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Apply theme palette to all Qt Quick Controls
|
// Apply theme palette to all Qt Quick Controls
|
||||||
palette.active.window: theme.windowColor
|
palette.active.window: theme.windowColor
|
||||||
palette.active.windowText: theme.windowTextColor
|
palette.active.windowText: theme.windowTextColor
|
||||||
|
|
|
||||||
|
|
@ -971,6 +971,36 @@ void Ollie9pClient::openDirectory(const QString &path)
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Ollie9pClient::activeSessionName() const
|
||||||
|
{
|
||||||
|
if (m_activeSessionId.isEmpty()) return QString();
|
||||||
|
for (const QVariant &v : m_sessions) {
|
||||||
|
QVariantMap session = v.toMap();
|
||||||
|
if (session.value("id").toString() == m_activeSessionId) {
|
||||||
|
return session.value("name").toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m_activeSessionId; // fallback to ID
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Ollie9pClient::activeAgentName() const
|
||||||
|
{
|
||||||
|
if (m_activeSessionId.isEmpty() || m_agentId.isEmpty()) return QString();
|
||||||
|
for (const QVariant &v : m_sessions) {
|
||||||
|
QVariantMap session = v.toMap();
|
||||||
|
if (session.value("id").toString() == m_activeSessionId) {
|
||||||
|
QVariantList agents = session.value("agents").toList();
|
||||||
|
for (const QVariant &a : agents) {
|
||||||
|
QVariantMap agent = a.toMap();
|
||||||
|
if (agent.value("id").toString() == m_agentId) {
|
||||||
|
return agent.value("name").toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m_agentId; // fallback to ID
|
||||||
|
}
|
||||||
|
|
||||||
// --- Peer management ---
|
// --- Peer management ---
|
||||||
|
|
||||||
QStringList Ollie9pClient::listPeers(const QString &sessionId, const QString &agentId)
|
QStringList Ollie9pClient::listPeers(const QString &sessionId, const QString &agentId)
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ class Ollie9pClient : public QObject
|
||||||
Q_PROPERTY(QVariantList sessions READ sessions NOTIFY sessionsChanged)
|
Q_PROPERTY(QVariantList sessions READ sessions NOTIFY sessionsChanged)
|
||||||
Q_PROPERTY(QString activeSessionId READ activeSessionId WRITE setActiveSessionId NOTIFY activeSessionIdChanged)
|
Q_PROPERTY(QString activeSessionId READ activeSessionId WRITE setActiveSessionId NOTIFY activeSessionIdChanged)
|
||||||
Q_PROPERTY(QString activeAgentId READ activeAgentId NOTIFY activeAgentIdChanged)
|
Q_PROPERTY(QString activeAgentId READ activeAgentId NOTIFY activeAgentIdChanged)
|
||||||
|
Q_PROPERTY(QString activeSessionName READ activeSessionName NOTIFY activeSessionIdChanged)
|
||||||
|
Q_PROPERTY(QString activeAgentName READ activeAgentName NOTIFY activeAgentIdChanged)
|
||||||
Q_PROPERTY(QString activeState READ activeState NOTIFY activeStateChanged)
|
Q_PROPERTY(QString activeState READ activeState NOTIFY activeStateChanged)
|
||||||
Q_PROPERTY(bool activeSessionPaused READ activeSessionPaused NOTIFY activeSessionPausedChanged)
|
Q_PROPERTY(bool activeSessionPaused READ activeSessionPaused NOTIFY activeSessionPausedChanged)
|
||||||
Q_PROPERTY(bool rootBackendsLoaded READ rootBackendsLoaded NOTIFY rootBackendsLoadedChanged)
|
Q_PROPERTY(bool rootBackendsLoaded READ rootBackendsLoaded NOTIFY rootBackendsLoadedChanged)
|
||||||
|
|
@ -42,6 +44,8 @@ public:
|
||||||
QString activeSessionId() const { return m_activeSessionId; }
|
QString activeSessionId() const { return m_activeSessionId; }
|
||||||
Q_INVOKABLE void setActiveSessionId(const QString &id);
|
Q_INVOKABLE void setActiveSessionId(const QString &id);
|
||||||
QString activeAgentId() const { return m_agentId; }
|
QString activeAgentId() const { return m_agentId; }
|
||||||
|
QString activeSessionName() const;
|
||||||
|
QString activeAgentName() const;
|
||||||
QString activeState() const { return m_activeState; }
|
QString activeState() const { return m_activeState; }
|
||||||
bool activeSessionPaused() const { return m_activeSessionPaused; }
|
bool activeSessionPaused() const { return m_activeSessionPaused; }
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue