gui: sync kf5 with kf6 features

- New session dialog with backend/model picker (replaces folder-only)
- Error dialog connected to dbus.onErrorOccurred
- Video sub-delegate (thumbnail card, opens in browser)
- Session graph: stateColor helper, zoom-toward-mouse
- placeholderText in palette block
This commit is contained in:
Levi Neely 2026-07-24 12:05:35 +02:00
parent 62663aff4e
commit 325ff53a54
1 changed files with 184 additions and 16 deletions

View File

@ -39,6 +39,7 @@ ApplicationWindow {
dark: theme.darkColor
light: theme.lightColor
shadow: theme.shadowColor
placeholderText: theme.placeholderTextColor
}
// --- Settings Dialog ---
@ -179,9 +180,77 @@ ApplicationWindow {
}
function openNewSessionDialog() {
var path = theme.chooseFolder("Choose working directory for new session")
newSessionDialog.backendModel = dbus.listBackends()
newSessionDialog.cwdField.text = dbus.activeCwd || ""
newSessionDialog.backendCombo.currentIndex = 0
newSessionDialog.modelCombo.editText = ""
newSessionDialog.open()
}
Dialog {
id: newSessionDialog
title: "New Session"
modal: true
anchors.centerIn: parent
width: 450
standardButtons: Dialog.Ok | Dialog.Cancel
property alias cwdField: cwdInput
property alias backendCombo: backendSelect
property alias modelCombo: modelSelect
property var backendModel: []
onAccepted: {
var cwd = cwdInput.text.trim()
if (cwd === "") cwd = dbus.activeCwd || ""
var backend = backendSelect.currentText
var model = modelSelect.editText.trim() || modelSelect.currentText
dbus.createSession(cwd, backend, model)
}
GridLayout {
columns: 2
width: parent.width
columnSpacing: 10
rowSpacing: 8
Label { text: "Working directory:" }
RowLayout {
Layout.fillWidth: true
TextField {
id: cwdInput
Layout.fillWidth: true
placeholderText: "/home/user/project"
}
Button {
text: "Browse"
onClicked: {
var path = theme.chooseFolder("Choose working directory", cwdInput.text)
if (path !== "")
dbus.createSession(path)
cwdInput.text = path
}
}
}
Label { text: "Backend:" }
ComboBox {
id: backendSelect
Layout.fillWidth: true
model: newSessionDialog.backendModel
onCurrentTextChanged: {
if (currentText)
modelSelect.model = dbus.listModelsByBackend(currentText)
}
}
Label { text: "Model:" }
ComboBox {
id: modelSelect
Layout.fillWidth: true
editable: true
editText: ""
}
}
}
function openCwdDialog() {
@ -209,6 +278,15 @@ ApplicationWindow {
property real panX: 0
property real panY: 0
property real zoom: 1.0
property real minZoom: 0.3
property real maxZoom: 3.0
function stateColor(state) {
if (state === "idle") return "#4caf50"
if (state && state.startsWith("calling")) return "#ff9800"
if (state === "thinking") return "#2196f3"
return "#2196f3"
}
function layoutNodes() {
var positions = {}
@ -257,7 +335,13 @@ ApplicationWindow {
}
onWheel: {
var factor = wheel.angleDelta.y > 0 ? 1.15 : 0.87
graphRoot.zoom = Math.max(0.3, Math.min(3.0, graphRoot.zoom * factor))
var newZoom = Math.max(graphRoot.minZoom, Math.min(graphRoot.maxZoom, graphRoot.zoom * factor))
// Zoom toward mouse position
var mx = wheel.x - graphRoot.panX
var my = wheel.y - graphRoot.panY
graphRoot.panX -= mx * (newZoom / graphRoot.zoom - 1)
graphRoot.panY -= my * (newZoom / graphRoot.zoom - 1)
graphRoot.zoom = newZoom
}
}
@ -324,12 +408,7 @@ ApplicationWindow {
height: 50
radius: 8
color: palette.base
border.color: {
var st = modelData.state
if (st === "idle") return "#4caf50"
if (st && st.startsWith("calling")) return "#ff9800"
return "#2196f3"
}
border.color: graphRoot.stateColor(modelData.state)
border.width: modelData.id === dbus.activeSessionId ? 3 : 2
x: {
var pos = graphRoot.nodePositions[modelData.id]
@ -364,12 +443,7 @@ ApplicationWindow {
font.pixelSize: 9
opacity: 0.7
anchors.horizontalCenter: parent.horizontalCenter
color: {
var st = modelData.state
if (st === "idle") return "#4caf50"
if (st && st.startsWith("calling")) return "#ff9800"
return "#2196f3"
}
color: graphRoot.stateColor(modelData.state)
}
}
@ -707,6 +781,24 @@ ApplicationWindow {
mainContent.panes[idx].sessionId = dbus.activeSessionId
}
}
function onErrorOccurred(message) {
errorDialog.text = message
errorDialog.open()
}
}
Dialog {
id: errorDialog
title: "Error"
modal: true
anchors.centerIn: parent
standardButtons: Dialog.Ok
property alias text: errorLabel.text
Label {
id: errorLabel
wrapMode: Text.Wrap
width: parent.width
}
}
// --- Inline ChatPane Component ---
@ -1251,6 +1343,7 @@ ApplicationWindow {
sourceComponent: subBlock.type === "code" ? codeSubDelegate
: subBlock.type === "diff" ? diffSubDelegate
: subBlock.type === "interrupt" ? interruptSubDelegate
: subBlock.type === "video" ? videoSubDelegate
: proseSubDelegate
}
}
@ -1315,6 +1408,7 @@ ApplicationWindow {
sourceComponent: subBlock.type === "code" ? codeSubDelegate
: subBlock.type === "diff" ? diffSubDelegate
: subBlock.type === "interrupt" ? interruptSubDelegate
: subBlock.type === "video" ? videoSubDelegate
: proseSubDelegate
}
}
@ -1414,6 +1508,7 @@ ApplicationWindow {
sourceComponent: subBlock.type === "code" ? codeSubDelegate
: subBlock.type === "diff" ? diffSubDelegate
: subBlock.type === "interrupt" ? interruptSubDelegate
: subBlock.type === "video" ? videoSubDelegate
: plainSubDelegate
}
}
@ -1475,6 +1570,7 @@ ApplicationWindow {
sourceComponent: subBlock.type === "code" ? codeSubDelegate
: subBlock.type === "diff" ? diffSubDelegate
: subBlock.type === "interrupt" ? interruptSubDelegate
: subBlock.type === "video" ? videoSubDelegate
: plainSubDelegate
}
}
@ -1591,6 +1687,78 @@ ApplicationWindow {
}
}
Component {
id: videoSubDelegate
Item {
id: videoItem
width: parent ? parent.width : 400
height: Math.max(200, Math.min(width * 9/16, 360))
property string videoUrl: subBlock.content || ""
property bool isDirectMedia: /\.(mp4|webm|ogv|m3u8)(\?|$)/i.test(videoUrl)
property string youtubeId: {
var m = videoUrl.match(/[?&]v=([^&]+)/) || videoUrl.match(/youtu\.be\/([^?]+)/)
return m ? m[1] : ""
}
// Non-direct: thumbnail card that opens in browser
Rectangle {
anchors.fill: parent
color: "#181818"
radius: 8
clip: true
// YouTube thumbnail
Image {
anchors.fill: parent
source: videoItem.youtubeId !== "" ?
"https://img.youtube.com/vi/" + videoItem.youtubeId + "/hqdefault.jpg" : ""
fillMode: Image.PreserveAspectCrop
visible: videoItem.youtubeId !== ""
}
// Play button overlay
Rectangle {
anchors.centerIn: parent
width: 72; height: 72
radius: 36
color: "#aa000000"
Text {
anchors.centerIn: parent
text: "\u25B6"
font.pixelSize: 36
color: "white"
}
}
// URL label at bottom
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 28
color: "#cc000000"
radius: 4
Text {
anchors.centerIn: parent
width: parent.width - 16
text: videoItem.videoUrl
color: "#ccc"
font.pixelSize: 11
elide: Text.ElideMiddle
horizontalAlignment: Text.AlignHCenter
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: Qt.openUrlExternally(videoItem.videoUrl)
}
}
}
}
Component {
id: codeSubDelegate
Rectangle {