incremental session tree updates
Instead of clearing and rebuilding the entire session tree model on every sessionsChanged signal, diff old vs new and: - Update existing session/agent properties in place - Remove deleted sessions/agents - Append new sessions/agents This avoids O(n) model rebuilds that cause ListView to re-render all delegates, improving responsiveness with many sessions.
This commit is contained in:
parent
0c93414251
commit
24921e7a2b
222
gui/main.qml
222
gui/main.qml
|
|
@ -109,52 +109,203 @@ ApplicationWindow {
|
|||
// Cache for session expanded state (sessionId -> bool)
|
||||
property var sessionExpandedCache: ({})
|
||||
|
||||
// Cache of current model structure for diffing: { sessionId: { agents: [agentId, ...] } }
|
||||
property var modelCache: ({})
|
||||
|
||||
function buildSessionTree() {
|
||||
// Preserve focus if prompt input has it
|
||||
// Full rebuild - used on initial load
|
||||
var hadFocus = promptInput.activeFocus
|
||||
sessionTreeModel.clear()
|
||||
var newCache = {}
|
||||
modelCache = {}
|
||||
var sessions = ollie.sessions
|
||||
for (var i = 0; i < sessions.length; ++i) {
|
||||
var sess = sessions[i]
|
||||
var sessionId = sess.id || ""
|
||||
var sessionName = sess.name || sessionId
|
||||
var agents = sess.agents || []
|
||||
var agentCount = agents.length
|
||||
var displayLabel = agentCount > 0 ? sessionName + " (" + agentCount + ")" : sessionName
|
||||
// Preserve expanded state from cache, default to true
|
||||
var wasExpanded = sessionExpandedCache[sessionId] !== undefined ? sessionExpandedCache[sessionId] : true
|
||||
newCache[sessionId] = wasExpanded
|
||||
appendSessionToModel(sess)
|
||||
}
|
||||
if (hadFocus)
|
||||
promptInput.forceActiveFocus()
|
||||
}
|
||||
|
||||
function appendSessionToModel(sess) {
|
||||
var sessionId = sess.id || ""
|
||||
var sessionName = sess.name || sessionId
|
||||
var agents = sess.agents || []
|
||||
var agentCount = agents.length
|
||||
var displayLabel = agentCount > 0 ? sessionName + " (" + agentCount + ")" : sessionName
|
||||
var wasExpanded = sessionExpandedCache[sessionId] !== undefined ? sessionExpandedCache[sessionId] : true
|
||||
sessionExpandedCache[sessionId] = wasExpanded
|
||||
|
||||
sessionTreeModel.append({
|
||||
type: "session",
|
||||
id: sessionId,
|
||||
level: 0,
|
||||
expanded: wasExpanded,
|
||||
state: sess.state || "",
|
||||
model: sess.model || "",
|
||||
displayName: displayLabel,
|
||||
baseName: sessionName,
|
||||
connectionColor: ollie.sessionConnectionColor(sessionId)
|
||||
})
|
||||
|
||||
var agentIds = []
|
||||
for (var j = 0; j < agents.length; ++j) {
|
||||
var agent = agents[j]
|
||||
var liveState = ollie.agentState(sessionId, agent.id)
|
||||
sessionTreeModel.append({
|
||||
type: "session",
|
||||
id: sessionId,
|
||||
level: 0,
|
||||
expanded: wasExpanded,
|
||||
state: sess.state || "",
|
||||
model: sess.model || "",
|
||||
displayName: displayLabel,
|
||||
baseName: sessionName, // For rename operations
|
||||
connectionColor: ollie.sessionConnectionColor(sessionId)
|
||||
type: "agent",
|
||||
parentId: sessionId,
|
||||
agentId: agent.id,
|
||||
level: 1,
|
||||
state: liveState || agent.state || "idle",
|
||||
connected: agent.connected,
|
||||
displayName: agent.name
|
||||
})
|
||||
for (var j = 0; j < agents.length; ++j) {
|
||||
var agent = agents[j]
|
||||
// Use live state if available, otherwise fall back to refresh-time state
|
||||
var liveState = ollie.agentState(sessionId, agent.id)
|
||||
sessionTreeModel.append({
|
||||
agentIds.push(agent.id)
|
||||
}
|
||||
modelCache[sessionId] = { agents: agentIds }
|
||||
}
|
||||
|
||||
function updateSessionTree() {
|
||||
// Incremental update - diff old vs new
|
||||
var hadFocus = promptInput.activeFocus
|
||||
var sessions = ollie.sessions
|
||||
|
||||
// Build lookup of new sessions
|
||||
var newSessionIds = {}
|
||||
for (var i = 0; i < sessions.length; ++i) {
|
||||
newSessionIds[sessions[i].id] = sessions[i]
|
||||
}
|
||||
|
||||
// Build lookup of old sessions from cache
|
||||
var oldSessionIds = Object.keys(modelCache)
|
||||
|
||||
// Find removed sessions and delete from model (iterate backwards to preserve indices)
|
||||
for (var idx = sessionTreeModel.count - 1; idx >= 0; --idx) {
|
||||
var item = sessionTreeModel.get(idx)
|
||||
if (item.type === "session" && !newSessionIds[item.id]) {
|
||||
// Remove session and all its agents
|
||||
removeSessionFromModel(idx, item.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Update existing sessions and add new ones
|
||||
for (var j = 0; j < sessions.length; ++j) {
|
||||
var sess = sessions[j]
|
||||
var sessionId = sess.id
|
||||
|
||||
if (modelCache[sessionId]) {
|
||||
// Session exists - update properties
|
||||
updateSessionInModel(sess)
|
||||
} else {
|
||||
// New session - append at end
|
||||
appendSessionToModel(sess)
|
||||
}
|
||||
}
|
||||
|
||||
if (hadFocus)
|
||||
promptInput.forceActiveFocus()
|
||||
}
|
||||
|
||||
function findSessionIndex(sessionId) {
|
||||
for (var i = 0; i < sessionTreeModel.count; ++i) {
|
||||
var item = sessionTreeModel.get(i)
|
||||
if (item.type === "session" && item.id === sessionId)
|
||||
return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
function removeSessionFromModel(sessionIdx, sessionId) {
|
||||
// Count agents to remove (they follow the session)
|
||||
var removeCount = 1
|
||||
for (var i = sessionIdx + 1; i < sessionTreeModel.count; ++i) {
|
||||
var item = sessionTreeModel.get(i)
|
||||
if (item.type === "session") break
|
||||
if (item.type === "agent" && item.parentId === sessionId)
|
||||
removeCount++
|
||||
}
|
||||
// Remove session + agents
|
||||
for (var r = 0; r < removeCount; ++r) {
|
||||
sessionTreeModel.remove(sessionIdx)
|
||||
}
|
||||
delete modelCache[sessionId]
|
||||
delete sessionExpandedCache[sessionId]
|
||||
}
|
||||
|
||||
function updateSessionInModel(sess) {
|
||||
var sessionId = sess.id
|
||||
var sessionIdx = findSessionIndex(sessionId)
|
||||
if (sessionIdx < 0) return
|
||||
|
||||
var sessionName = sess.name || sessionId
|
||||
var agents = sess.agents || []
|
||||
var agentCount = agents.length
|
||||
var displayLabel = agentCount > 0 ? sessionName + " (" + agentCount + ")" : sessionName
|
||||
|
||||
// Update session properties
|
||||
sessionTreeModel.setProperty(sessionIdx, "displayName", displayLabel)
|
||||
sessionTreeModel.setProperty(sessionIdx, "baseName", sessionName)
|
||||
sessionTreeModel.setProperty(sessionIdx, "state", sess.state || "")
|
||||
sessionTreeModel.setProperty(sessionIdx, "model", sess.model || "")
|
||||
sessionTreeModel.setProperty(sessionIdx, "connectionColor", ollie.sessionConnectionColor(sessionId))
|
||||
|
||||
// Build agent lookup
|
||||
var newAgentIds = {}
|
||||
for (var i = 0; i < agents.length; ++i) {
|
||||
newAgentIds[agents[i].id] = agents[i]
|
||||
}
|
||||
var oldAgentIds = modelCache[sessionId] ? modelCache[sessionId].agents : []
|
||||
|
||||
// Update/remove existing agents
|
||||
for (var idx = sessionIdx + 1; idx < sessionTreeModel.count; ++idx) {
|
||||
var item = sessionTreeModel.get(idx)
|
||||
if (item.type === "session") break
|
||||
if (item.type === "agent" && item.parentId === sessionId) {
|
||||
if (newAgentIds[item.agentId]) {
|
||||
// Update agent
|
||||
var agent = newAgentIds[item.agentId]
|
||||
var liveState = ollie.agentState(sessionId, agent.id)
|
||||
sessionTreeModel.setProperty(idx, "state", liveState || agent.state || "idle")
|
||||
sessionTreeModel.setProperty(idx, "connected", agent.connected)
|
||||
sessionTreeModel.setProperty(idx, "displayName", agent.name)
|
||||
} else {
|
||||
// Remove agent
|
||||
sessionTreeModel.remove(idx)
|
||||
--idx // Adjust for removed item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add new agents (at end of session's agents)
|
||||
var insertIdx = sessionIdx + 1
|
||||
for (var k = insertIdx; k < sessionTreeModel.count; ++k) {
|
||||
var checkItem = sessionTreeModel.get(k)
|
||||
if (checkItem.type === "session") break
|
||||
insertIdx = k + 1
|
||||
}
|
||||
|
||||
var newAgentList = []
|
||||
for (var m = 0; m < agents.length; ++m) {
|
||||
var ag = agents[m]
|
||||
newAgentList.push(ag.id)
|
||||
var found = false
|
||||
for (var n = 0; n < oldAgentIds.length; ++n) {
|
||||
if (oldAgentIds[n] === ag.id) { found = true; break }
|
||||
}
|
||||
if (!found) {
|
||||
var liveState2 = ollie.agentState(sessionId, ag.id)
|
||||
sessionTreeModel.insert(insertIdx, {
|
||||
type: "agent",
|
||||
parentId: sessionId,
|
||||
agentId: agent.id,
|
||||
agentId: ag.id,
|
||||
level: 1,
|
||||
state: liveState || agent.state || "idle",
|
||||
connected: agent.connected,
|
||||
displayName: agent.name
|
||||
state: liveState2 || ag.state || "idle",
|
||||
connected: ag.connected,
|
||||
displayName: ag.name
|
||||
})
|
||||
}
|
||||
}
|
||||
sessionExpandedCache = newCache
|
||||
// Restore focus if it was on the prompt input
|
||||
if (hadFocus)
|
||||
promptInput.forceActiveFocus()
|
||||
modelCache[sessionId] = { agents: newAgentList }
|
||||
}
|
||||
|
||||
function toggleExpand(index) {
|
||||
|
|
@ -218,7 +369,12 @@ ApplicationWindow {
|
|||
Connections {
|
||||
target: ollie
|
||||
function onSessionsChanged() {
|
||||
buildSessionTree()
|
||||
// Use incremental update if model exists, full rebuild otherwise
|
||||
if (sessionTreeModel.count > 0 && Object.keys(modelCache).length > 0) {
|
||||
updateSessionTree()
|
||||
} else {
|
||||
buildSessionTree()
|
||||
}
|
||||
}
|
||||
function onAgentStateChanged(sessionId, agentId, state) {
|
||||
// Increment version to force re-evaluation of all agent state colors
|
||||
|
|
|
|||
Reference in New Issue