kf5: sync gui with kf6 features, fix install paths
- main-kf5.qml: port all KF6 gui enhancements (chat bubbles, structured blocks, markdown, syntax highlighting, diff coloring, collapsible call/tool blocks, lazy-load scrolling) - CMakeLists.txt: add chatblockmodel.cpp and KF5::SyntaxHighlighting to KF5 gui target; fix Kate plugin INSTALL_NAMESPACE (ktexteditor, not kf5/ktexteditor or kf6/ktexteditor) - Makefile: fix KF6 kate install path; add icon+desktop to kf5-install - main.qml + main-kf5.qml: fix prose font size (use root.font.pointSize instead of Qt.application.font.pixelSize)
This commit is contained in:
parent
f546c8cf69
commit
f6b218bef7
|
|
@ -31,6 +31,7 @@ if(OLLIE_KF5)
|
|||
Config
|
||||
Plasma
|
||||
TextEditor
|
||||
SyntaxHighlighting
|
||||
)
|
||||
|
||||
# --- System Tray ---
|
||||
|
|
@ -58,7 +59,7 @@ if(OLLIE_KF5)
|
|||
)
|
||||
|
||||
# --- Kate Plugin ---
|
||||
kcoreaddons_add_plugin(ollie_kate SOURCES kate/ollie_kate.cpp kate/ollie_ghost.cpp INSTALL_NAMESPACE "kf5/ktexteditor")
|
||||
kcoreaddons_add_plugin(ollie_kate SOURCES kate/ollie_kate.cpp kate/ollie_ghost.cpp INSTALL_NAMESPACE "ktexteditor")
|
||||
target_link_libraries(ollie_kate
|
||||
KF5::TextEditor
|
||||
Qt5::Core
|
||||
|
|
@ -94,6 +95,7 @@ if(OLLIE_KF5)
|
|||
add_executable(ollie-gui
|
||||
gui/main.cpp
|
||||
gui/thememanager.cpp
|
||||
gui/chatblockmodel.cpp
|
||||
plasmoid/plugin/olliedbusclient.cpp
|
||||
${GUI_KF5_QRC}
|
||||
)
|
||||
|
|
@ -109,6 +111,7 @@ if(OLLIE_KF5)
|
|||
Qt5::Qml
|
||||
Qt5::QuickControls2
|
||||
KF5::ConfigCore
|
||||
KF5::SyntaxHighlighting
|
||||
)
|
||||
|
||||
# --- Install ---
|
||||
|
|
@ -156,7 +159,7 @@ else()
|
|||
|
||||
# --- Kate Plugin ---
|
||||
find_package(KF6TextEditor REQUIRED)
|
||||
kcoreaddons_add_plugin(ollie_kate SOURCES kate/ollie_kate.cpp kate/ollie_ghost.cpp INSTALL_NAMESPACE "kf6/ktexteditor")
|
||||
kcoreaddons_add_plugin(ollie_kate SOURCES kate/ollie_kate.cpp kate/ollie_ghost.cpp INSTALL_NAMESPACE "ktexteditor")
|
||||
target_link_libraries(ollie_kate
|
||||
KF6::TextEditor
|
||||
Qt6::Core
|
||||
|
|
|
|||
13
Makefile
13
Makefile
|
|
@ -39,9 +39,9 @@ install: build
|
|||
install -m755 $(BUILD_DIR)/lib/kf6/krunner/krunner_ollie.so \
|
||||
/usr/lib64/qt6/plugins/kf6/krunner/
|
||||
# Kate plugin
|
||||
mkdir -p /usr/lib64/qt6/plugins/kf6/ktexteditor
|
||||
install -m755 $(BUILD_DIR)/lib/kf6/ktexteditor/ollie_kate.so \
|
||||
/usr/lib64/qt6/plugins/kf6/ktexteditor/
|
||||
mkdir -p /usr/lib64/qt6/plugins/ktexteditor
|
||||
install -m755 $(BUILD_DIR)/lib/ktexteditor/ollie_kate.so \
|
||||
/usr/lib64/qt6/plugins/ktexteditor/
|
||||
# Plasmoid QML plugin library + metadata
|
||||
mkdir -p /usr/lib64/qt6/qml/org/ollie/private
|
||||
install -m755 $(BUILD_DIR)/org/ollie/private/ollie-plasmoid-pluginplugin.so \
|
||||
|
|
@ -74,7 +74,7 @@ uninstall:
|
|||
rm -f /usr/share/icons/hicolor/scalable/apps/ollie.svg
|
||||
rm -f /usr/lib64/libollie-plasmoid-plugin.so
|
||||
rm -f /usr/lib64/qt6/plugins/kf6/krunner/krunner_ollie.so
|
||||
rm -f /usr/lib64/qt6/plugins/kf6/ktexteditor/ollie_kate.so
|
||||
rm -f /usr/lib64/qt6/plugins/ktexteditor/ollie_kate.so
|
||||
rm -rf /usr/lib64/qt6/qml/org/ollie
|
||||
kpackagetool6 -g -t Plasma/Applet -r org.ollie.plasmoid 2>/dev/null; true
|
||||
rm -f ~/.local/share/kio/servicemenus/ollie-actions.desktop
|
||||
|
|
@ -128,6 +128,11 @@ kf5-install:
|
|||
# Binaries
|
||||
install -m755 $(KF5_BUILD_DIR)/ollie-tray /usr/bin/
|
||||
install -m755 $(KF5_BUILD_DIR)/ollie-gui /usr/bin/
|
||||
# Desktop file and icon
|
||||
mkdir -p /usr/share/applications
|
||||
install -m644 gui/org.ollie.gui.desktop /usr/share/applications/
|
||||
mkdir -p /usr/share/icons/hicolor/scalable/apps
|
||||
install -m644 gui/ollie.svg /usr/share/icons/hicolor/scalable/apps/ollie.svg
|
||||
# KRunner plugin
|
||||
mkdir -p /usr/lib/x86_64-linux-gnu/qt5/plugins/kf5/krunner
|
||||
install -m755 $(KF5_BUILD_DIR)/lib/krunner_ollie.so \
|
||||
|
|
|
|||
457
gui/main-kf5.qml
457
gui/main-kf5.qml
|
|
@ -8,6 +8,7 @@ ApplicationWindow {
|
|||
width: 900
|
||||
height: 600
|
||||
visible: true
|
||||
|
||||
color: palette.window
|
||||
font.family: theme.fontFamily || Qt.application.font.family
|
||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
|
||||
|
|
@ -42,6 +43,7 @@ ApplicationWindow {
|
|||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 16
|
||||
Layout.margins: 16
|
||||
|
||||
Label {
|
||||
text: "Color Scheme"
|
||||
|
|
@ -346,43 +348,83 @@ ApplicationWindow {
|
|||
color: palette.mid
|
||||
}
|
||||
|
||||
// Chat view
|
||||
ScrollView {
|
||||
id: chatScroll
|
||||
// Chat view — structured blocks
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
property bool atBottom: true
|
||||
|
||||
Binding {
|
||||
target: chatModel
|
||||
property: "rawText"
|
||||
value: dbus.chatText || ""
|
||||
}
|
||||
|
||||
Flickable {
|
||||
id: chatFlickable
|
||||
contentWidth: chatArea.width
|
||||
contentHeight: chatArea.implicitHeight
|
||||
id: chatFlick
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
clip: true
|
||||
contentWidth: width
|
||||
contentHeight: chatColumn.implicitHeight
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
onContentYChanged: {
|
||||
var bottom = contentHeight - height
|
||||
chatScroll.atBottom = (bottom <= 0 || contentY >= bottom - 4)
|
||||
parent.atBottom = (bottom <= 0 || contentY >= bottom - 4)
|
||||
|
||||
// Load earlier messages when scrolled near top
|
||||
if (contentY < 100 && contentHeight > height) {
|
||||
var oldHeight = contentHeight
|
||||
if (chatModel.loadEarlier()) {
|
||||
Qt.callLater(function() {
|
||||
chatFlick.contentY += (chatFlick.contentHeight - oldHeight)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
onContentHeightChanged: {
|
||||
if (chatScroll.atBottom) {
|
||||
if (parent.atBottom) {
|
||||
contentY = Math.max(0, contentHeight - height)
|
||||
}
|
||||
}
|
||||
|
||||
TextArea {
|
||||
id: chatArea
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
width: chatFlickable.width
|
||||
wrapMode: TextEdit.Wrap
|
||||
text: dbus.chatText || (dbus.activeSessionId
|
||||
? "(waiting for output...)"
|
||||
: "Select or create a session")
|
||||
font.family: "monospace"
|
||||
color: dbus.chatText ? palette.text : theme.placeholderTextColor
|
||||
background: null
|
||||
Column {
|
||||
id: chatColumn
|
||||
width: chatFlick.width
|
||||
spacing: 8
|
||||
|
||||
// Placeholder
|
||||
Label {
|
||||
visible: chatRepeater.count === 0
|
||||
text: dbus.activeSessionId
|
||||
? "(waiting for output...)"
|
||||
: "Select or create a session"
|
||||
color: theme.placeholderTextColor
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: chatRepeater
|
||||
model: chatModel
|
||||
|
||||
delegate: Loader {
|
||||
width: chatColumn.width
|
||||
property var block: model
|
||||
sourceComponent: {
|
||||
switch (blockType) {
|
||||
case "user": return userDelegate
|
||||
case "assistant": return assistantDelegate
|
||||
case "reasoning": return reasoningDelegate
|
||||
case "call": return callDelegate
|
||||
case "tool": return toolDelegate
|
||||
case "error": return errorDelegate
|
||||
case "info": return infoDelegate
|
||||
default: return assistantDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -391,10 +433,10 @@ ApplicationWindow {
|
|||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 6
|
||||
icon.name: "go-down"
|
||||
visible: !chatScroll.atBottom
|
||||
visible: !parent.atBottom
|
||||
onClicked: {
|
||||
chatFlickable.contentY = Math.max(0, chatFlickable.contentHeight - chatFlickable.height)
|
||||
chatScroll.atBottom = true
|
||||
chatFlick.contentY = Math.max(0, chatFlick.contentHeight - chatFlick.height)
|
||||
parent.atBottom = true
|
||||
}
|
||||
ToolTip.text: "Scroll to bottom"
|
||||
ToolTip.visible: hovered
|
||||
|
|
@ -472,4 +514,373 @@ ApplicationWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Chat block delegates ---
|
||||
|
||||
Component {
|
||||
id: userDelegate
|
||||
Item {
|
||||
implicitHeight: userBubble.implicitHeight + userLabel.implicitHeight + 4
|
||||
|
||||
Rectangle {
|
||||
id: userBubble
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: Math.min(userContent.implicitWidth + 24, parent.width * 0.85)
|
||||
implicitHeight: userContent.implicitHeight + 16
|
||||
color: Qt.rgba(palette.highlight.r, palette.highlight.g, palette.highlight.b, 0.15)
|
||||
radius: 12
|
||||
|
||||
ColumnLayout {
|
||||
id: userContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 4
|
||||
|
||||
Repeater {
|
||||
model: block.subBlocks
|
||||
delegate: Loader {
|
||||
Layout.fillWidth: true
|
||||
property var subBlock: modelData
|
||||
sourceComponent: subBlock.type === "code" ? codeSubDelegate
|
||||
: subBlock.type === "diff" ? diffSubDelegate
|
||||
: proseSubDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: userLabel
|
||||
text: "You"
|
||||
font.pixelSize: Qt.application.font.pixelSize * 0.75
|
||||
opacity: 0.5
|
||||
anchors.right: parent.right
|
||||
anchors.top: userBubble.bottom
|
||||
anchors.topMargin: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: assistantDelegate
|
||||
Item {
|
||||
implicitHeight: assistantBubble.implicitHeight + assistantLabel.implicitHeight + 4
|
||||
|
||||
Rectangle {
|
||||
id: assistantBubble
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: Math.min(assistantContent.implicitWidth + 24, parent.width * 0.95)
|
||||
implicitHeight: assistantContent.implicitHeight + 16
|
||||
color: Qt.rgba(palette.text.r, palette.text.g, palette.text.b, 0.06)
|
||||
radius: 12
|
||||
|
||||
ColumnLayout {
|
||||
id: assistantContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 4
|
||||
|
||||
Repeater {
|
||||
model: block.subBlocks
|
||||
delegate: Loader {
|
||||
Layout.fillWidth: true
|
||||
property var subBlock: modelData
|
||||
sourceComponent: subBlock.type === "code" ? codeSubDelegate
|
||||
: subBlock.type === "diff" ? diffSubDelegate
|
||||
: proseSubDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: assistantLabel
|
||||
text: "Ollie"
|
||||
font.pixelSize: Qt.application.font.pixelSize * 0.75
|
||||
opacity: 0.5
|
||||
anchors.left: parent.left
|
||||
anchors.top: assistantBubble.bottom
|
||||
anchors.topMargin: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: reasoningDelegate
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Label {
|
||||
text: (reasoningText.visible ? "\u25BC" : "\u25B6") + " reasoning"
|
||||
font.pixelSize: Qt.application.font.pixelSize * 0.8
|
||||
opacity: 0.5
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: reasoningText.visible = !reasoningText.visible
|
||||
}
|
||||
}
|
||||
Label {
|
||||
id: reasoningText
|
||||
Layout.fillWidth: true
|
||||
text: block.blockContent
|
||||
wrapMode: Text.Wrap
|
||||
font.family: "monospace"
|
||||
font.pixelSize: Qt.application.font.pixelSize * 0.85
|
||||
opacity: 0.5
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: callDelegate
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
|
||||
property bool expanded: false
|
||||
|
||||
Label {
|
||||
text: (parent.expanded ? "\u25BC" : "\u25B6") + " call: " + (block.blockName || "")
|
||||
font.pixelSize: Qt.application.font.pixelSize * 0.8
|
||||
opacity: 0.5
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: parent.parent.expanded = !parent.parent.expanded
|
||||
}
|
||||
}
|
||||
|
||||
// Call args — pretty-printed highlighted HTML
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: block.formattedContent || ""
|
||||
textFormat: Text.RichText
|
||||
wrapMode: Text.Wrap
|
||||
font.family: "monospace"
|
||||
font.pixelSize: Qt.application.font.pixelSize * 0.85
|
||||
opacity: 0.7
|
||||
visible: parent.expanded
|
||||
color: palette.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: toolDelegate
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
|
||||
property bool expanded: false
|
||||
|
||||
Label {
|
||||
text: (parent.expanded ? "\u25BC" : "\u25B6") + " tool: " + (block.blockName || "")
|
||||
font.pixelSize: Qt.application.font.pixelSize * 0.8
|
||||
opacity: 0.5
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: parent.parent.expanded = !parent.parent.expanded
|
||||
}
|
||||
}
|
||||
|
||||
// Render sub-blocks (same as assistant) when expanded
|
||||
Repeater {
|
||||
model: parent.expanded ? block.subBlocks : []
|
||||
delegate: Loader {
|
||||
Layout.fillWidth: true
|
||||
property var subBlock: modelData
|
||||
sourceComponent: subBlock.type === "code" ? codeSubDelegate
|
||||
: subBlock.type === "diff" ? diffSubDelegate
|
||||
: proseSubDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: errorDelegate
|
||||
Rectangle {
|
||||
implicitHeight: errorLabel.implicitHeight + 12
|
||||
color: Qt.rgba(1, 0, 0, 0.08)
|
||||
radius: 4
|
||||
Label {
|
||||
id: errorLabel
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
text: block.blockContent
|
||||
wrapMode: Text.Wrap
|
||||
color: "#e53935"
|
||||
font.family: "monospace"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: infoDelegate
|
||||
Label {
|
||||
text: block.blockContent
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: Qt.application.font.pixelSize * 0.85
|
||||
opacity: 0.6
|
||||
font.italic: true
|
||||
}
|
||||
}
|
||||
|
||||
// --- Sub-block delegates ---
|
||||
|
||||
Component {
|
||||
id: proseSubDelegate
|
||||
Text {
|
||||
width: parent ? parent.width : implicitWidth
|
||||
text: subBlock.content || ""
|
||||
wrapMode: Text.Wrap
|
||||
textFormat: Text.MarkdownText
|
||||
color: palette.text
|
||||
font.pointSize: root.font.pointSize
|
||||
font.family: root.font.family
|
||||
lineHeight: 1.3
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: codeSubDelegate
|
||||
Rectangle {
|
||||
implicitHeight: codeCol.implicitHeight
|
||||
color: Qt.rgba(palette.base.r, palette.base.g, palette.base.b, 0.6)
|
||||
radius: 4
|
||||
border.width: 1
|
||||
border.color: palette.mid
|
||||
|
||||
ColumnLayout {
|
||||
id: codeCol
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// Header with language + copy button
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 4
|
||||
|
||||
Label {
|
||||
text: subBlock.language || "code"
|
||||
font.pixelSize: Qt.application.font.pixelSize * 0.8
|
||||
opacity: 0.6
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
icon.name: "edit-copy"
|
||||
implicitWidth: 22
|
||||
implicitHeight: 22
|
||||
onClicked: {
|
||||
chatModel.copyToClipboard(subBlock.content)
|
||||
}
|
||||
ToolTip.text: "Copy"
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
|
||||
// Code content — show plain first, highlight lazily
|
||||
Text {
|
||||
id: codeText
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 8
|
||||
text: subBlock.content
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.Wrap
|
||||
font.family: "monospace"
|
||||
font.pixelSize: Qt.application.font.pixelSize * 0.9
|
||||
color: palette.text
|
||||
|
||||
property bool highlighted: false
|
||||
Timer {
|
||||
interval: 150
|
||||
running: true
|
||||
onTriggered: {
|
||||
codeText.text = chatModel.highlightedHtml(subBlock.content, subBlock.language)
|
||||
codeText.textFormat = Text.RichText
|
||||
codeText.highlighted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: diffSubDelegate
|
||||
Rectangle {
|
||||
implicitHeight: diffCol.implicitHeight
|
||||
color: Qt.rgba(palette.base.r, palette.base.g, palette.base.b, 0.6)
|
||||
radius: 4
|
||||
border.width: 1
|
||||
border.color: palette.mid
|
||||
|
||||
ColumnLayout {
|
||||
id: diffCol
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 4
|
||||
|
||||
Label {
|
||||
text: "diff"
|
||||
font.pixelSize: Qt.application.font.pixelSize * 0.8
|
||||
opacity: 0.6
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
icon.name: "edit-copy"
|
||||
implicitWidth: 22
|
||||
implicitHeight: 22
|
||||
onClicked: {
|
||||
chatModel.copyToClipboard(subBlock.content)
|
||||
}
|
||||
ToolTip.text: "Copy"
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
|
||||
// Diff lines with coloring
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 8
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: subBlock.content.split("\n")
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
implicitHeight: diffLineText.implicitHeight
|
||||
color: {
|
||||
if (modelData.startsWith("+")) return Qt.rgba(0.3, 0.8, 0.3, 0.08)
|
||||
if (modelData.startsWith("-")) return Qt.rgba(0.8, 0.3, 0.3, 0.08)
|
||||
return "transparent"
|
||||
}
|
||||
Text {
|
||||
id: diffLineText
|
||||
width: parent.width
|
||||
text: modelData
|
||||
font.family: "monospace"
|
||||
font.pixelSize: Qt.application.font.pixelSize * 0.9
|
||||
wrapMode: Text.Wrap
|
||||
color: {
|
||||
if (modelData.startsWith("+")) return "#4caf50"
|
||||
if (modelData.startsWith("-")) return "#f44336"
|
||||
if (modelData.startsWith("@@")) return "#2196f3"
|
||||
return palette.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -742,7 +742,8 @@ ApplicationWindow {
|
|||
wrapMode: Text.Wrap
|
||||
textFormat: Text.MarkdownText
|
||||
color: palette.text
|
||||
font.pixelSize: Qt.application.font.pixelSize
|
||||
font.pointSize: root.font.pointSize
|
||||
font.family: root.font.family
|
||||
lineHeight: 1.3
|
||||
onLinkActivated: function(link) {
|
||||
Qt.openUrlExternally(link)
|
||||
|
|
|
|||
Reference in New Issue