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 var blockSegments
|
||||
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
|
||||
property bool blockExpanded: {
|
||||
|
|
@ -179,6 +189,35 @@ Column {
|
|||
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() {
|
||||
chatView.positionViewAtEnd()
|
||||
}
|
||||
function scrollToBlock(blockIndex) {
|
||||
if (blockIndex >= 0 && blockIndex < chatModel.count) {
|
||||
chatView.positionViewAtIndex(blockIndex, ListView.Center)
|
||||
}
|
||||
}
|
||||
function trimVisibleBlocks() {
|
||||
if (chatModel.count > chatPane.maxVisibleBlocks)
|
||||
chatModel.trimFront(chatPane.maxVisibleBlocks)
|
||||
|
|
@ -265,6 +270,9 @@ Item {
|
|||
|
||||
delegate: ChatBlockDelegate {
|
||||
width: chatView.width - 8
|
||||
addBookmark: root.addBookmark
|
||||
removeBookmark: root.removeBookmark
|
||||
checkBookmarked: root.isBookmarked
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ Item {
|
|||
id: sessionList
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 100
|
||||
model: sessionModel
|
||||
clip: true
|
||||
focus: false
|
||||
|
|
@ -366,6 +367,102 @@ Item {
|
|||
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) {
|
||||
var item = sessionModel.get(index)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,54 @@ ApplicationWindow {
|
|||
font.family: theme.fontFamily || Qt.application.font.family
|
||||
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
|
||||
palette.active.window: theme.windowColor
|
||||
palette.active.windowText: theme.windowTextColor
|
||||
|
|
|
|||
|
|
@ -971,6 +971,36 @@ void Ollie9pClient::openDirectory(const QString &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 ---
|
||||
|
||||
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(QString activeSessionId READ activeSessionId WRITE setActiveSessionId NOTIFY activeSessionIdChanged)
|
||||
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(bool activeSessionPaused READ activeSessionPaused NOTIFY activeSessionPausedChanged)
|
||||
Q_PROPERTY(bool rootBackendsLoaded READ rootBackendsLoaded NOTIFY rootBackendsLoadedChanged)
|
||||
|
|
@ -42,6 +44,8 @@ public:
|
|||
QString activeSessionId() const { return m_activeSessionId; }
|
||||
Q_INVOKABLE void setActiveSessionId(const QString &id);
|
||||
QString activeAgentId() const { return m_agentId; }
|
||||
QString activeSessionName() const;
|
||||
QString activeAgentName() const;
|
||||
QString activeState() const { return m_activeState; }
|
||||
bool activeSessionPaused() const { return m_activeSessionPaused; }
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue