import QtQuick import QtQuick.Controls import QtQuick.Layouts // A tool call: what ran, whether it is still running, and what came back. // Long output stays folded until asked for, diff lines are colored. Rectangle { id: card property string name: "" property string state: "pending" // pending | ready | running | done | denied // | unfinished (replayed, no result) property string detail: "" property string output: "" property int exitCode: 0 property bool hasResult: false property bool expanded: false property string images: "" readonly property bool running: state === "pending" || state === "ready" || state === "running" readonly property color stateColor: state === "denied" ? Theme.err : state === "unfinished" ? Theme.textFaint : running ? Theme.warn : hasResult && exitCode !== 0 ? Theme.err : Theme.ok readonly property string stateText: state === "pending" ? "reading" : state === "ready" ? "queued" : state === "running" ? "running" : state === "denied" ? "denied" : state === "unfinished" ? "no result" : hasResult && exitCode !== 0 ? "failed" : "done" function lines() { const all = output.split("\n") return all.length > 500 ? all.slice(0, 500).concat(["… " + (all.length - 500) + " more lines"]) : all } radius: Theme.radius color: Theme.surfaceAlt border.width: 1 border.color: expanded ? Theme.stateColor : Theme.border implicitHeight: layout.implicitHeight + Theme.pad ColumnLayout { id: layout anchors { left: parent.left right: parent.right top: parent.top margins: Theme.pad } spacing: 6 RowLayout { spacing: Theme.gap BusyIndicator { visible: card.running running: card.running implicitWidth: 16 implicitHeight: 16 Layout.alignment: Qt.AlignVCenter } Rectangle { visible: !card.running Layout.alignment: Qt.AlignVCenter width: 6 height: 6 radius: 3 color: card.stateColor } /* What kind of tool this was, from the icon theme (Theme.toolIcon * maps the engine's names onto Breeze ones). */ ThemeIcon { Layout.alignment: Qt.AlignVCenter name: Theme.toolIcon(card.name) size: 16 color: Theme.textDim } Text { text: card.name color: Theme.text font.family: Theme.mono font.pixelSize: Theme.monoSize font.bold: true } Rectangle { Layout.alignment: Qt.AlignVCenter radius: Theme.radiusSm color: "transparent" border.width: 1 border.color: card.stateColor implicitWidth: chip.implicitWidth + 10 implicitHeight: chip.implicitHeight + 2 Text { id: chip anchors.centerIn: parent /* "no result" is not an outcome: no exit code belongs on it. */ text: card.stateText + (card.hasResult && card.state !== "unfinished" ? " · exit " + card.exitCode : "") color: card.stateColor font.pixelSize: Theme.smallSize } } Text { Layout.fillWidth: true elide: Text.ElideRight text: card.detail.replace(/\s+/g, " ").trim() color: Theme.textFaint font.family: Theme.mono font.pixelSize: Theme.smallSize } ToolButton { implicitWidth: 26 implicitHeight: 22 visible: card.output.length > 0 contentItem: ThemeIcon { name: card.expanded ? "arrow-down" : "arrow-right" glyph: card.expanded ? "▾" : "▸" size: 14 color: parent.hovered ? Theme.text : Theme.textDim } background: Rectangle { radius: Theme.radiusSm color: parent.hovered ? Theme.surfaceHi : "transparent" } onClicked: card.expanded = !card.expanded } } // What the tool made (view_image, an MCP image): shown as it is, with // the output left folded -- the picture is the result. ImageStrip { Layout.fillWidth: true visible: card.images.length > 0 text: card.images } // Folded view: the first line of the result, so a done card still says // something without being opened. Text { Layout.fillWidth: true visible: !card.expanded && card.output.length > 0 text: { const l = card.output.split("\n") return l[0] + (l.length > 1 ? " …" : "") } elide: Text.ElideRight color: Theme.textFaint font.family: Theme.mono font.pixelSize: Theme.smallSize } ColumnLayout { Layout.fillWidth: true visible: card.expanded && card.output.length > 0 spacing: 0 Repeater { model: card.expanded ? card.lines() : [] Text { Layout.fillWidth: true text: modelData.length ? modelData : " " color: Theme.lineColor(modelData) font.family: Theme.mono font.pixelSize: Theme.monoSize wrapMode: Text.NoWrap textFormat: Text.PlainText } } Text { Layout.topMargin: 4 visible: card.output.split("\n").length > 500 text: "output truncated for the view" color: Theme.textFaint font.pixelSize: Theme.smallSize } } } }