gui: convert ProcManager to separate Window, sync to active session

- Convert from Dialog to Window (not modal, separate window)
- Title shows current session name
- Syncs to activeSessionId/activeAgentId changes in main window
- Header shows which session is being displayed
- Double-click row to view output
- Sort processes by PID descending (newest first)
- Clear selection when session changes
This commit is contained in:
Levi Neely 2026-10-08 14:57:25 +02:00
parent 491f5ff2ae
commit 4774cb9fe9
5 changed files with 104 additions and 34 deletions

View File

@ -145,7 +145,7 @@ qt_add_resources(ollie-gui "gui"
gui/NewSessionDialog.qml gui/NewSessionDialog.qml
gui/NewAgentDialog.qml gui/NewAgentDialog.qml
gui/GoalDialog.qml gui/GoalDialog.qml
gui/ProcManagerDialog.qml gui/ProcManagerWindow.qml
gui/ChatBlockDelegate.qml gui/ChatBlockDelegate.qml
gui/SessionTree.qml gui/SessionTree.qml
gui/ChatPane.qml gui/ChatPane.qml

View File

@ -361,7 +361,7 @@ Item {
text: "⚙️ Procs" text: "⚙️ Procs"
font.pixelSize: theme.fontSize > 0 ? theme.fontSize * 0.9 : 11 font.pixelSize: theme.fontSize > 0 ? theme.fontSize * 0.9 : 11
implicitHeight: 28 implicitHeight: 28
onClicked: procManagerDialog.open() onClicked: procManagerWindow.show()
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: "Background processes" ToolTip.text: "Background processes"
} }

View File

@ -1,39 +1,85 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Window
Dialog { Window {
id: procDialog id: procWindow
title: "Background Processes" title: "Background Processes" + (currentSessionName ? " — " + currentSessionName : "")
width: 600 width: 650
height: 400 height: 450
modal: false minimumWidth: 400
standardButtons: Dialog.Close minimumHeight: 300
property var procs: [] property var procs: []
property string outputText: "" property string outputText: ""
property int selectedPid: -1 property int selectedPid: -1
// Track the session we're displaying (synced from main window)
property string currentSessionId: ollie.activeSessionId
property string currentAgentId: ollie.activeAgentId
property string currentSessionName: ""
function refresh() { function refresh() {
procs = ollie.listProcs(ollie.activeSessionId, ollie.activeAgentId) if (!currentSessionId || !currentAgentId) {
procs = []
return
}
var rawProcs = ollie.listProcs(currentSessionId, currentAgentId)
// Sort by PID descending (newest first)
rawProcs.sort(function(a, b) { return b.pid - a.pid })
procs = rawProcs
}
function updateSessionName() {
if (currentSessionId) {
var cfg = ollie.readSessionConfig(currentSessionId)
currentSessionName = cfg.name || currentSessionId
} else {
currentSessionName = ""
}
} }
Timer { Timer {
id: refreshTimer id: refreshTimer
interval: 2000 interval: 2000
running: procDialog.visible running: procWindow.visible
repeat: true repeat: true
onTriggered: refresh() onTriggered: refresh()
} }
onVisibleChanged: { onVisibleChanged: {
if (visible) refresh() if (visible) {
currentSessionId = ollie.activeSessionId
currentAgentId = ollie.activeAgentId
updateSessionName()
refresh()
}
} }
// Sync to active session/agent changes
Connections { Connections {
target: ollie target: ollie
function onActiveSessionIdChanged() {
if (procWindow.visible) {
currentSessionId = ollie.activeSessionId
currentAgentId = ollie.activeAgentId
updateSessionName()
selectedPid = -1
outputText = ""
refresh()
}
}
function onActiveAgentIdChanged() {
if (procWindow.visible) {
currentAgentId = ollie.activeAgentId
selectedPid = -1
outputText = ""
refresh()
}
}
function onProcStarted(sessionId, agentId, payload) { function onProcStarted(sessionId, agentId, payload) {
if (sessionId === ollie.activeSessionId && agentId === ollie.activeAgentId) { if (procWindow.visible && sessionId === currentSessionId && agentId === currentAgentId) {
refresh() refresh()
} }
} }
@ -41,9 +87,25 @@ Dialog {
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: 8 anchors.margins: 12
spacing: 8 spacing: 8
// Header showing current session/agent
RowLayout {
Layout.fillWidth: true
spacing: 8
Label {
text: "Session:"
font.bold: true
}
Label {
text: currentSessionName || "(none)"
Layout.fillWidth: true
elide: Text.ElideMiddle
}
}
// Process list // Process list
ListView { ListView {
id: procList id: procList
@ -52,14 +114,20 @@ Dialog {
model: procs model: procs
clip: true clip: true
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
delegate: Rectangle { delegate: Rectangle {
width: procList.width width: procList.width
height: 48 height: 44
color: selectedPid === modelData.pid ? palette.highlight : (index % 2 ? palette.alternateBase : palette.base) color: selectedPid === modelData.pid ? palette.highlight : (index % 2 ? palette.alternateBase : palette.base)
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: selectedPid = modelData.pid onClicked: selectedPid = modelData.pid
onDoubleClicked: {
selectedPid = modelData.pid
outputText = ollie.procOutput(currentSessionId, currentAgentId, selectedPid)
}
} }
RowLayout { RowLayout {
@ -69,17 +137,17 @@ Dialog {
// Status indicator // Status indicator
Rectangle { Rectangle {
width: 12 width: 10
height: 12 height: 10
radius: 6 radius: 5
color: modelData.status === "running" ? "green" : (modelData.exitCode === 0 ? "gray" : "red") color: modelData.status === "running" ? "#4CAF50" : (modelData.exitCode === 0 ? "#9E9E9E" : "#F44336")
} }
// PID // PID
Label { Label {
text: modelData.pid text: modelData.pid
font.family: "monospace" font.family: "monospace"
Layout.preferredWidth: 60 Layout.preferredWidth: 50
} }
// Tool // Tool
@ -102,17 +170,17 @@ Dialog {
Label { Label {
text: formatRuntime(modelData.runtime) text: formatRuntime(modelData.runtime)
font.family: "monospace" font.family: "monospace"
Layout.preferredWidth: 60 Layout.preferredWidth: 55
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignRight
} }
// Exit code (if exited) // Exit code (if exited)
Label { Label {
visible: modelData.status !== "running" visible: modelData.status !== "running"
text: "exit " + modelData.exitCode text: modelData.exitCode === 0 ? "✓" : "exit " + modelData.exitCode
font.family: "monospace" font.family: "monospace"
color: modelData.exitCode === 0 ? "gray" : "red" color: modelData.exitCode === 0 ? "#4CAF50" : "#F44336"
Layout.preferredWidth: 50 Layout.preferredWidth: 45
} }
} }
} }
@ -120,15 +188,15 @@ Dialog {
Label { Label {
anchors.centerIn: parent anchors.centerIn: parent
visible: procs.length === 0 visible: procs.length === 0
text: "No background processes" text: currentSessionId ? "No background processes" : "No session selected"
opacity: 0.5 opacity: 0.5
} }
} }
// Output pane (collapsible) // Output pane (expandable)
GroupBox { GroupBox {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: outputText ? 150 : 0 Layout.preferredHeight: outputText.length > 0 ? 150 : 0
visible: outputText.length > 0 visible: outputText.length > 0
title: "Output (PID " + selectedPid + ")" title: "Output (PID " + selectedPid + ")"
@ -140,6 +208,7 @@ Dialog {
font.family: "monospace" font.family: "monospace"
font.pointSize: 9 font.pointSize: 9
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
selectByMouse: true
} }
} }
} }
@ -160,7 +229,7 @@ Dialog {
icon.name: "document-preview" icon.name: "document-preview"
enabled: selectedPid > 0 enabled: selectedPid > 0
onClicked: { onClicked: {
outputText = ollie.procOutput(ollie.activeSessionId, ollie.activeAgentId, selectedPid) outputText = ollie.procOutput(currentSessionId, currentAgentId, selectedPid)
} }
} }
@ -171,7 +240,7 @@ Dialog {
icon.name: "process-stop" icon.name: "process-stop"
enabled: selectedPid > 0 && isRunning(selectedPid) enabled: selectedPid > 0 && isRunning(selectedPid)
onClicked: { onClicked: {
ollie.procTerm(ollie.activeSessionId, ollie.activeAgentId, selectedPid) ollie.procTerm(currentSessionId, currentAgentId, selectedPid)
Qt.callLater(refresh) Qt.callLater(refresh)
} }
} }
@ -181,7 +250,7 @@ Dialog {
icon.name: "edit-delete" icon.name: "edit-delete"
enabled: selectedPid > 0 && isRunning(selectedPid) enabled: selectedPid > 0 && isRunning(selectedPid)
onClicked: { onClicked: {
ollie.procKill(ollie.activeSessionId, ollie.activeAgentId, selectedPid) ollie.procKill(currentSessionId, currentAgentId, selectedPid)
Qt.callLater(refresh) Qt.callLater(refresh)
} }
} }

View File

@ -189,9 +189,10 @@ ApplicationWindow {
id: goalDialog id: goalDialog
} }
// --- Process Manager Dialog --- // --- Process Manager Window ---
ProcManagerDialog { ProcManagerWindow {
id: procManagerDialog id: procManagerWindow
visible: false
} }
SplitView { SplitView {

View File

@ -5,7 +5,7 @@
<file>NewSessionDialog.qml</file> <file>NewSessionDialog.qml</file>
<file>NewAgentDialog.qml</file> <file>NewAgentDialog.qml</file>
<file>GoalDialog.qml</file> <file>GoalDialog.qml</file>
<file>ProcManagerDialog.qml</file> <file>ProcManagerWindow.qml</file>
<file>ChatBlockDelegate.qml</file> <file>ChatBlockDelegate.qml</file>
<file>SessionTree.qml</file> <file>SessionTree.qml</file>
<file>ChatPane.qml</file> <file>ChatPane.qml</file>