C++ / fmt (pull_request) Successful in 6s
C++ / build (pull_request) Failing after 2m2s
C++ / clang-tidy (pull_request) Failing after 2m0s
JS/TS / fmt (pull_request) Failing after 11s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 27s
Rust / build (pull_request) Successful in 50s
Python / verify (pull_request) Successful in 1m32s
Rust / fmt (pull_request) Successful in 27s
Rust / clippy (pull_request) Successful in 48s
168 lines
4.4 KiB
QML
168 lines
4.4 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts
|
|
import ZShell.Llm
|
|
import ZShell.Config
|
|
import qs.Modules.Notifications.Sidebar.Chat
|
|
import qs.Components
|
|
import qs.Services
|
|
|
|
Item {
|
|
id: root
|
|
|
|
required property bool isUser
|
|
required property LlmSegment segment
|
|
required property bool hovered
|
|
required property Repeater repeater
|
|
required property int index
|
|
required property ChatMessage message
|
|
required property ChatGeneration current
|
|
|
|
readonly property real contentMaxWidth: width - Tokens.spacing.extraSmall - Tokens.spacing.extraLarge
|
|
|
|
signal edit(text: string)
|
|
|
|
implicitHeight: bubble.implicitHeight + actionsRow.implicitHeight + actionsRow.anchors.topMargin
|
|
|
|
CustomClippingRect {
|
|
id: bubble
|
|
|
|
property real liveTargetWidth: root.isUser ? Math.min(msgText.implicitWidth + Tokens.padding.medium * 2, root.contentMaxWidth) : root.contentMaxWidth
|
|
property real frozenWidth: 0.0
|
|
property bool layoutFrozen: false
|
|
property real layoutWidth: root.isUser ? Math.min(msgText.implicitWidth + Tokens.padding.medium * 2, root.contentMaxWidth) : root.contentMaxWidth
|
|
|
|
radius: Tokens.rounding.medium
|
|
color: root.isUser ? Colors.palette.m3primary : Colors.palette.m3surfaceContainer
|
|
implicitWidth: layoutFrozen ? frozenWidth : layoutWidth
|
|
implicitHeight: root.isUser ? msgText.contentHeight + Tokens.padding.medium * 2 : blocks.implicitHeight + blocks.anchors.topMargin * 2
|
|
anchors.right: root.isUser ? parent.right : undefined
|
|
layer.enabled: false
|
|
layer.smooth: true
|
|
|
|
transform: Scale {
|
|
id: stretchScale
|
|
origin.x: root.isUser ? bubble.width : 0
|
|
xScale: bubble.layoutFrozen && bubble.frozenWidth > 0 ? bubble.liveTargetWidth / bubble.frozenWidth : 1.0
|
|
}
|
|
|
|
states: [
|
|
State {
|
|
name: "toSlim"
|
|
when: !ChatState.isWide && !ChatState.settledSlim
|
|
|
|
PropertyChanges {
|
|
bubble.layer.enabled: true
|
|
bubble.layoutFrozen: true
|
|
}
|
|
},
|
|
State {
|
|
name: "toWide"
|
|
when: ChatState.isWide && !ChatState.settledWide
|
|
|
|
PropertyChanges {
|
|
bubble.layer.enabled: true
|
|
bubble.layoutFrozen: true
|
|
}
|
|
}
|
|
]
|
|
|
|
onLayoutFrozenChanged: {
|
|
if (layoutFrozen)
|
|
frozenWidth = width;
|
|
}
|
|
|
|
TextEditBase {
|
|
id: msgText
|
|
|
|
property string cachedText: root.segment.text
|
|
property bool cancelled: false
|
|
|
|
visible: root.isUser
|
|
anchors.left: parent.left
|
|
anchors.margins: Tokens.padding.medium
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
animateCursor: false
|
|
color: root.isUser ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
|
cursor.color: root.isUser ? Colors.palette.m3onPrimary : Colors.palette.m3primary
|
|
font.pointSize: Tokens.font.size.smaller
|
|
readOnly: true
|
|
selectionColor: Qt.alpha((root.isUser ? Colors.palette.m3onPrimary : Colors.palette.m3primary), 0.4)
|
|
text: root.isUser ? root.segment.text : ""
|
|
textFormat: CustomText.MarkdownText
|
|
wrapMode: Text.WordWrap
|
|
|
|
Keys.onPressed: event => {
|
|
if (event.key == Qt.Key_Return) {
|
|
if (!(event.modifiers & Qt.ShiftModifier)) {
|
|
readOnly = true;
|
|
event.accepted = true;
|
|
}
|
|
} else if (event.key == Qt.Key_Escape) {
|
|
cancelled = true;
|
|
readOnly = true;
|
|
event.accepted = true;
|
|
}
|
|
}
|
|
onEditingFinished: {
|
|
readOnly = true;
|
|
}
|
|
onReadOnlyChanged: {
|
|
if (readOnly) {
|
|
animateCursor = false;
|
|
textFormat = CustomText.MarkdownText;
|
|
|
|
if (cancelled) {
|
|
text = cachedText;
|
|
cancelled = false;
|
|
return;
|
|
}
|
|
|
|
root.edit(text);
|
|
} else {
|
|
textFormat = CustomText.PlainText;
|
|
text = cachedText;
|
|
forceActiveFocus();
|
|
cursorPosition = text.length;
|
|
animateCursor = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Assistant content: parsed markdown blocks.
|
|
MarkdownBlocks {
|
|
id: blocks
|
|
|
|
visible: !root.isUser
|
|
anchors.topMargin: Tokens.padding.medium
|
|
anchors.top: parent.top
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
blocks: root.segment.markdown
|
|
}
|
|
}
|
|
|
|
Actions {
|
|
id: actionsRow
|
|
|
|
readonly property bool shouldBeActive: (root.segment.type === LlmSegment.Type.Content) && !root.segment.running && root.repeater.count === (root.index + 1)
|
|
|
|
anchors.left: parent.left
|
|
anchors.right: (implicitWidth > bubble.implicitWidth) ? undefined : bubble.right
|
|
anchors.top: bubble.bottom
|
|
opacity: shouldBeActive ? 1 : 0
|
|
visible: opacity > 0
|
|
anchors.topMargin: Tokens.spacing.medium
|
|
edit: msgText
|
|
hovered: root.hovered
|
|
isUser: root.isUser
|
|
segment: root.segment
|
|
current: root.current
|
|
message: root.message
|
|
|
|
Behavior on opacity {
|
|
Anim {}
|
|
}
|
|
}
|
|
}
|