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:
parent
62663aff4e
commit
325ff53a54
200
gui/main-kf5.qml
200
gui/main-kf5.qml
|
|
@ -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")
|
||||
if (path !== "")
|
||||
dbus.createSession(path)
|
||||
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 !== "")
|
||||
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 {
|
||||
|
|
|
|||
Reference in New Issue