fix(gui): improve multi-agent-per-session handling

Session selection now auto-selects first agent:
- Clicking a session row selects its first agent (not just the session)
- Auto-select at startup now picks first agent of first session
- Fixes issue where clicking session would clear agent and show empty chat

Improved UI feedback:
- Header shows 'Session / Agent' name instead of just state
- State indicator gray when no agent selected
- Placeholder message when no agent selected: 'Select an agent to view chat'
- Chat ListView hidden when no agent (shows placeholder instead)
- Input already disabled with 'No agent' placeholder (existing behavior)

Cleanup:
- Remove dead declaration reconcileAgentConnections(const QVariantList&)
This commit is contained in:
Levi Neely 2026-08-03 17:42:05 +02:00
parent 28de16dbf1
commit 414143955f
3 changed files with 70 additions and 8 deletions

View File

@ -627,7 +627,22 @@ ApplicationWindow {
} }
if (renamingIndex >= 0) return if (renamingIndex >= 0) return
if (type === "session") { if (type === "session") {
ollie.setActiveSessionId(id) // Find first agent in this session and select it
var foundAgent = null
for (var i = 0; i < sessionTreeModel.count; ++i) {
var item = sessionTreeModel.get(i)
if (item.type === "agent" && item.parentId === id) {
foundAgent = item
break
}
}
if (foundAgent) {
// Select the first agent
activateAgent(id, foundAgent.agentId)
} else {
// No agents - just select session (clears agent)
ollie.setActiveSessionId(id)
}
toggleExpand(parent.modelIndex) toggleExpand(parent.modelIndex)
} else if (type === "agent") { } else if (type === "agent") {
activateAgent(parentId, agentId) activateAgent(parentId, agentId)
@ -773,15 +788,44 @@ ApplicationWindow {
ColumnLayout { ColumnLayout {
spacing: 0 spacing: 0
// Header // Header - shows active session/agent and state
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 6 Layout.margins: 6
spacing: 8 spacing: 8
// Active session/agent label
Label {
text: {
var sessionName = ""
var agentName = ""
for (var i = 0; i < sessionTreeModel.count; ++i) {
var item = sessionTreeModel.get(i)
if (item.type === "session" && item.id === ollie.activeSessionId) {
sessionName = item.displayName || item.id
}
if (item.type === "agent" && item.agentId === ollie.activeAgentId && item.parentId === ollie.activeSessionId) {
agentName = item.displayName || item.agentId
}
}
if (sessionName && agentName) {
return sessionName + " / " + agentName
} else if (sessionName) {
return sessionName + " (no agent)"
}
return "(no session)"
}
font.bold: true
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
elide: Text.ElideMiddle
Layout.fillWidth: true
}
// State indicator
Rectangle { Rectangle {
width: 10; height: 10; radius: 5 width: 10; height: 10; radius: 5
color: { color: {
if (!ollie.activeAgentId) return "#9e9e9e" // gray when no agent
if (ollie.activeState === "idle") return "#4caf50" if (ollie.activeState === "idle") return "#4caf50"
if (ollie.activeState.startsWith("calling")) return "#ff9800" if (ollie.activeState.startsWith("calling")) return "#ff9800"
return "#2196f3" return "#2196f3"
@ -789,12 +833,10 @@ ApplicationWindow {
} }
Label { Label {
text: ollie.activeState text: ollie.activeAgentId ? ollie.activeState : ""
opacity: 0.7 opacity: 0.7
font.pointSize: theme.fontSize > 0 ? theme.fontSize * 0.85 : 11 font.pointSize: theme.fontSize > 0 ? theme.fontSize * 0.85 : 11
} }
Item { Layout.fillWidth: true }
} }
Rectangle { Layout.fillWidth: true; height: 1; color: theme.midColor } Rectangle { Layout.fillWidth: true; height: 1; color: theme.midColor }
@ -805,6 +847,16 @@ ApplicationWindow {
Layout.fillHeight: true Layout.fillHeight: true
clip: true clip: true
// Placeholder when no agent selected
Label {
anchors.centerIn: parent
text: ollie.activeSessionId ? "Select an agent to view chat" : "Select a session"
color: theme.midColor
font.italic: true
font.pixelSize: theme.fontSize > 0 ? theme.fontSize : 12
visible: !ollie.activeAgentId
}
ListView { ListView {
id: chatView id: chatView
model: chatModel model: chatModel
@ -812,6 +864,7 @@ ApplicationWindow {
anchors.fill: parent anchors.fill: parent
anchors.margins: 4 anchors.margins: 4
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
visible: ollie.activeAgentId !== ""
onCountChanged: { onCountChanged: {
if (count > 0) if (count > 0)

View File

@ -330,9 +330,19 @@ void Ollie9pClient::refreshSessions()
reconcileAgentConnections(); reconcileAgentConnections();
emit sessionsChanged(); emit sessionsChanged();
// Auto-select first session if none active // Auto-select first session and its first agent if none active
if (m_activeSessionId.isEmpty() && !m_sessions.isEmpty()) { if (m_activeSessionId.isEmpty() && !m_sessions.isEmpty()) {
setActiveSessionId(m_sessions.first().toMap()["id"].toString()); const QVariantMap firstSession = m_sessions.first().toMap();
const QString sid = firstSession.value("id").toString();
const QVariantList agents = firstSession.value("agents").toList();
if (!agents.isEmpty()) {
// Select first agent of first session
const QString aid = agents.first().toMap().value("id").toString();
switchAgent(sid, aid);
} else {
// No agents - just select the session
setActiveSessionId(sid);
}
} }
} }

View File

@ -76,7 +76,6 @@ public:
}; };
QList<AgentRecord> readAgentRecords(const QString &sessionId); QList<AgentRecord> readAgentRecords(const QString &sessionId);
void reconcileAgentConnections(const QVariantList &sessions);
void setAgentConnected(const QString &key, bool connected); void setAgentConnected(const QString &key, bool connected);
QString agentKey(const QString &sessionId, const QString &agentId) const; QString agentKey(const QString &sessionId, const QString &agentId) const;
Q_INVOKABLE void setActiveAgentId(const QString &agentId); Q_INVOKABLE void setActiveAgentId(const QString &agentId);