import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 ApplicationWindow { id: root visible: true width: 1000 height: 600 title: "Ollie" // Chat tail buffer — only keep last 8KB for display property string chatTail: "" readonly property int maxTail: 8192 Component.onCompleted: { // Load initial log content var log = ollie.readLog() if (log.length > maxTail) log = log.slice(-maxTail) chatTail = log } Connections { target: ollie function onChatReceived(text) { chatTail += text if (chatTail.length > maxTail * 2) { chatTail = chatTail.slice(-maxTail) } } function onActiveSessionIdChanged() { chatTail = "" var log = ollie.readLog() if (log.length > maxTail) log = log.slice(-maxTail) chatTail = log } function onStreamingDone() { // Session was killed chatTail += "\n[session ended]\n" } } SplitView { anchors.fill: parent // Left pane: session list ColumnLayout { SplitView.preferredWidth: 200 SplitView.minimumWidth: 120 spacing: 0 ToolBar { Layout.fillWidth: true RowLayout { anchors.fill: parent Label { text: "Sessions"; Layout.fillWidth: true; font.bold: true; leftPadding: 8 } ToolButton { text: "↻" onClicked: ollie.refreshSessions() } } } ListView { id: sessionList Layout.fillWidth: true Layout.fillHeight: true model: ollie.sessions clip: true delegate: ItemDelegate { width: sessionList.width text: modelData.id || "" highlighted: (modelData.id === ollie.activeSessionId) onClicked: ollie.activeSessionId = modelData.id Rectangle { width: 8; height: 8; radius: 4 anchors.right: parent.right anchors.rightMargin: 8 anchors.verticalCenter: parent.verticalCenter color: { var s = modelData.state || "idle" if (s === "idle") return "#4caf50" if (s.startsWith("calling")) return "#ff9800" return "#2196f3" } } } } } // Right pane: chat + input ColumnLayout { spacing: 0 // Header RowLayout { Layout.fillWidth: true Layout.margins: 6 spacing: 8 Rectangle { width: 10; height: 10; radius: 5 color: { if (ollie.activeState === "idle") return "#4caf50" if (ollie.activeState.startsWith("calling")) return "#ff9800" return "#2196f3" } } Label { text: ollie.activeState opacity: 0.7 font.pixelSize: 11 } Item { Layout.fillWidth: true } ToolButton { text: "Stop" onClicked: ollie.interrupt() } } Rectangle { Layout.fillWidth: true; height: 1; color: palette.mid } // Chat display — streaming text, last 8KB ScrollView { Layout.fillWidth: true Layout.fillHeight: true TextArea { id: chatArea readOnly: true wrapMode: TextEdit.NoWrap text: root.chatTail font.family: "monospace" font.pixelSize: 12 onTextChanged: cursorPosition = text.length } } Rectangle { Layout.fillWidth: true; height: 1; color: palette.mid } // Input RowLayout { Layout.fillWidth: true Layout.margins: 6 spacing: 4 TextField { id: promptInput Layout.fillWidth: true placeholderText: ollie.activeSessionId ? "Message..." : "No session" enabled: ollie.activeSessionId !== "" onAccepted: { if (text.trim() !== "") { ollie.submit(text.trim()) text = "" } } } Button { text: "Send" enabled: promptInput.text.trim() !== "" && ollie.activeSessionId !== "" onClicked: promptInput.accepted() } } } } }