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
|
Config
|
||||||
Plasma
|
Plasma
|
||||||
TextEditor
|
TextEditor
|
||||||
|
SyntaxHighlighting
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- System Tray ---
|
# --- System Tray ---
|
||||||
|
|
@ -58,7 +59,7 @@ if(OLLIE_KF5)
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Kate Plugin ---
|
# --- 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
|
target_link_libraries(ollie_kate
|
||||||
KF5::TextEditor
|
KF5::TextEditor
|
||||||
Qt5::Core
|
Qt5::Core
|
||||||
|
|
@ -94,6 +95,7 @@ if(OLLIE_KF5)
|
||||||
add_executable(ollie-gui
|
add_executable(ollie-gui
|
||||||
gui/main.cpp
|
gui/main.cpp
|
||||||
gui/thememanager.cpp
|
gui/thememanager.cpp
|
||||||
|
gui/chatblockmodel.cpp
|
||||||
plasmoid/plugin/olliedbusclient.cpp
|
plasmoid/plugin/olliedbusclient.cpp
|
||||||
${GUI_KF5_QRC}
|
${GUI_KF5_QRC}
|
||||||
)
|
)
|
||||||
|
|
@ -109,6 +111,7 @@ if(OLLIE_KF5)
|
||||||
Qt5::Qml
|
Qt5::Qml
|
||||||
Qt5::QuickControls2
|
Qt5::QuickControls2
|
||||||
KF5::ConfigCore
|
KF5::ConfigCore
|
||||||
|
KF5::SyntaxHighlighting
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Install ---
|
# --- Install ---
|
||||||
|
|
@ -156,7 +159,7 @@ else()
|
||||||
|
|
||||||
# --- Kate Plugin ---
|
# --- Kate Plugin ---
|
||||||
find_package(KF6TextEditor REQUIRED)
|
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
|
target_link_libraries(ollie_kate
|
||||||
KF6::TextEditor
|
KF6::TextEditor
|
||||||
Qt6::Core
|
Qt6::Core
|
||||||
|
|
|
||||||
13
Makefile
13
Makefile
|
|
@ -39,9 +39,9 @@ install: build
|
||||||
install -m755 $(BUILD_DIR)/lib/kf6/krunner/krunner_ollie.so \
|
install -m755 $(BUILD_DIR)/lib/kf6/krunner/krunner_ollie.so \
|
||||||
/usr/lib64/qt6/plugins/kf6/krunner/
|
/usr/lib64/qt6/plugins/kf6/krunner/
|
||||||
# Kate plugin
|
# Kate plugin
|
||||||
mkdir -p /usr/lib64/qt6/plugins/kf6/ktexteditor
|
mkdir -p /usr/lib64/qt6/plugins/ktexteditor
|
||||||
install -m755 $(BUILD_DIR)/lib/kf6/ktexteditor/ollie_kate.so \
|
install -m755 $(BUILD_DIR)/lib/ktexteditor/ollie_kate.so \
|
||||||
/usr/lib64/qt6/plugins/kf6/ktexteditor/
|
/usr/lib64/qt6/plugins/ktexteditor/
|
||||||
# Plasmoid QML plugin library + metadata
|
# Plasmoid QML plugin library + metadata
|
||||||
mkdir -p /usr/lib64/qt6/qml/org/ollie/private
|
mkdir -p /usr/lib64/qt6/qml/org/ollie/private
|
||||||
install -m755 $(BUILD_DIR)/org/ollie/private/ollie-plasmoid-pluginplugin.so \
|
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/share/icons/hicolor/scalable/apps/ollie.svg
|
||||||
rm -f /usr/lib64/libollie-plasmoid-plugin.so
|
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/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
|
rm -rf /usr/lib64/qt6/qml/org/ollie
|
||||||
kpackagetool6 -g -t Plasma/Applet -r org.ollie.plasmoid 2>/dev/null; true
|
kpackagetool6 -g -t Plasma/Applet -r org.ollie.plasmoid 2>/dev/null; true
|
||||||
rm -f ~/.local/share/kio/servicemenus/ollie-actions.desktop
|
rm -f ~/.local/share/kio/servicemenus/ollie-actions.desktop
|
||||||
|
|
@ -128,6 +128,11 @@ kf5-install:
|
||||||
# Binaries
|
# Binaries
|
||||||
install -m755 $(KF5_BUILD_DIR)/ollie-tray /usr/bin/
|
install -m755 $(KF5_BUILD_DIR)/ollie-tray /usr/bin/
|
||||||
install -m755 $(KF5_BUILD_DIR)/ollie-gui /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
|
# KRunner plugin
|
||||||
mkdir -p /usr/lib/x86_64-linux-gnu/qt5/plugins/kf5/krunner
|
mkdir -p /usr/lib/x86_64-linux-gnu/qt5/plugins/kf5/krunner
|
||||||
install -m755 $(KF5_BUILD_DIR)/lib/krunner_ollie.so \
|
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
|
width: 900
|
||||||
height: 600
|
height: 600
|
||||||
visible: true
|
visible: true
|
||||||
|
|
||||||
color: palette.window
|
color: palette.window
|
||||||
font.family: theme.fontFamily || Qt.application.font.family
|
font.family: theme.fontFamily || Qt.application.font.family
|
||||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
|
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
|
||||||
|
|
@ -42,6 +43,7 @@ ApplicationWindow {
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
contentItem: ColumnLayout {
|
||||||
spacing: 16
|
spacing: 16
|
||||||
|
Layout.margins: 16
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: "Color Scheme"
|
text: "Color Scheme"
|
||||||
|
|
@ -346,43 +348,83 @@ ApplicationWindow {
|
||||||
color: palette.mid
|
color: palette.mid
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chat view
|
// Chat view — structured blocks
|
||||||
ScrollView {
|
Item {
|
||||||
id: chatScroll
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
property bool atBottom: true
|
property bool atBottom: true
|
||||||
|
|
||||||
|
Binding {
|
||||||
|
target: chatModel
|
||||||
|
property: "rawText"
|
||||||
|
value: dbus.chatText || ""
|
||||||
|
}
|
||||||
|
|
||||||
Flickable {
|
Flickable {
|
||||||
id: chatFlickable
|
id: chatFlick
|
||||||
contentWidth: chatArea.width
|
anchors.fill: parent
|
||||||
contentHeight: chatArea.implicitHeight
|
anchors.margins: 4
|
||||||
clip: true
|
clip: true
|
||||||
|
contentWidth: width
|
||||||
|
contentHeight: chatColumn.implicitHeight
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
|
||||||
onContentYChanged: {
|
onContentYChanged: {
|
||||||
var bottom = contentHeight - height
|
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: {
|
onContentHeightChanged: {
|
||||||
if (chatScroll.atBottom) {
|
if (parent.atBottom) {
|
||||||
contentY = Math.max(0, contentHeight - height)
|
contentY = Math.max(0, contentHeight - height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextArea {
|
Column {
|
||||||
id: chatArea
|
id: chatColumn
|
||||||
readOnly: true
|
width: chatFlick.width
|
||||||
selectByMouse: true
|
spacing: 8
|
||||||
width: chatFlickable.width
|
|
||||||
wrapMode: TextEdit.Wrap
|
// Placeholder
|
||||||
text: dbus.chatText || (dbus.activeSessionId
|
Label {
|
||||||
? "(waiting for output...)"
|
visible: chatRepeater.count === 0
|
||||||
: "Select or create a session")
|
text: dbus.activeSessionId
|
||||||
font.family: "monospace"
|
? "(waiting for output...)"
|
||||||
color: dbus.chatText ? palette.text : theme.placeholderTextColor
|
: "Select or create a session"
|
||||||
background: null
|
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.bottom: parent.bottom
|
||||||
anchors.margins: 6
|
anchors.margins: 6
|
||||||
icon.name: "go-down"
|
icon.name: "go-down"
|
||||||
visible: !chatScroll.atBottom
|
visible: !parent.atBottom
|
||||||
onClicked: {
|
onClicked: {
|
||||||
chatFlickable.contentY = Math.max(0, chatFlickable.contentHeight - chatFlickable.height)
|
chatFlick.contentY = Math.max(0, chatFlick.contentHeight - chatFlick.height)
|
||||||
chatScroll.atBottom = true
|
parent.atBottom = true
|
||||||
}
|
}
|
||||||
ToolTip.text: "Scroll to bottom"
|
ToolTip.text: "Scroll to bottom"
|
||||||
ToolTip.visible: hovered
|
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
|
wrapMode: Text.Wrap
|
||||||
textFormat: Text.MarkdownText
|
textFormat: Text.MarkdownText
|
||||||
color: palette.text
|
color: palette.text
|
||||||
font.pixelSize: Qt.application.font.pixelSize
|
font.pointSize: root.font.pointSize
|
||||||
|
font.family: root.font.family
|
||||||
lineHeight: 1.3
|
lineHeight: 1.3
|
||||||
onLinkActivated: function(link) {
|
onLinkActivated: function(link) {
|
||||||
Qt.openUrlExternally(link)
|
Qt.openUrlExternally(link)
|
||||||
|
|
|
||||||
Reference in New Issue