new chatlistview component, file attachments, performance & primitive test

This commit is contained in:
2026-09-30 15:01:28 +02:00
parent 51ed270774
commit d8a3d5732e
36 changed files with 4368 additions and 310 deletions
@@ -22,7 +22,6 @@ Item {
readonly property real totalElapsedMs: root.segments.reduce((sum, s) => sum + (s.elapsedMs ?? 0), 0)
readonly property bool pendingApproval: current.toolApprovalPending && segments.filter(s => s.type === LlmSegment.Type.ToolCall).some(s => current.pendingToolCalls.includes(s))
clip: true
implicitHeight: {
const expand = expandedRect.implicitHeight + layout.implicitHeight + expandedRect.anchors.topMargin;
const collapsed = layout.implicitHeight;
@@ -36,6 +35,8 @@ Item {
return collapsed;
}
onCurrentChanged: expanded = false
Behavior on implicitHeight {
Anim {
type: Anim.DefaultEffects
@@ -147,19 +148,30 @@ Item {
property bool open: false
property Item rootParent: root.rootParent
property int openHeight: toolsBg.rootParent.height - Tokens.padding.medium * 2
property int maxHeight: rootParent.height - Tokens.padding.medium * 2
property int openHeight: Math.min(toolsContent.item?.implicitHeight, maxHeight)
property int openWidth: toolsBg.rootParent.width - Tokens.padding.medium * 2
property real openHeightProg: openHeight / maxHeight
property int collapsedY: 0
implicitHeight: toolList.implicitHeight + toolList.anchors.margins * 2
z: open || dialogTransition.running ? 2 : 0
Layout.fillWidth: true
function reparentWrapper(): void {
const newParent = open ? toolsBg.rootParent : toolsBg;
const pos = toolsWrapper.mapToItem(newParent, 0, 0);
const c = toolsWrapper.parent.mapToItem(newParent, toolsWrapper.x + toolsWrapper.width / 2, toolsWrapper.y + toolsWrapper.height / 2);
toolsBg.collapsedY = toolsWrapper.y;
toolsWrapper.parent = newParent;
toolsWrapper.x = pos.x;
toolsWrapper.y = pos.y;
console.log("X, Y:\n" + toolsWrapper.x, toolsWrapper.y, "\nWidth, Height:\n" + toolsWrapper.width, toolsWrapper.height);
toolsWrapper.x = c.x - toolsWrapper.width / 2;
toolsWrapper.y = c.y - toolsWrapper.height / 2;
}
onOpenChanged: {
if (open)
toolsBg.rootParent.interactive = false;
else
toolsBg.rootParent.interactive = true;
}
BlobGroup {
@@ -189,6 +201,8 @@ Item {
width: toolsBg.width
height: toolBox.implicitHeight
rotation: parent === toolsBg.rootParent ? 180 : 0
z: 1
states: State {
name: "open"
@@ -200,11 +214,11 @@ Item {
toolsBackground.bottomRightRadius: Tokens.rounding.largeIncreased
toolsBackground.topRightRadius: Tokens.rounding.largeIncreased
toolsBackground.topLeftRadius: Tokens.rounding.largeIncreased
// toolsContent.opacity: 1
toolsContent.opacity: 1
toolsWrapper.height: toolsBg.openHeight
toolsWrapper.width: toolsBg.openWidth
toolsWrapper.x: (toolsBg.rootParent.width - toolsBg.openWidth) / 2
toolsWrapper.y: (toolsBg.rootParent.height - toolsBg.openHeight) / 2
toolsWrapper.y: (confirmationRow.implicitHeight + Tokens.spacing.small + Tokens.padding.medium) * (1 - toolsBg.openHeightProg) + Tokens.padding.medium
elevation.opacity: 1
toolBox.opacity: 0
}
@@ -274,7 +288,6 @@ Item {
StateLayer {
onClicked: {
console.log("X, Y:\n" + toolsWrapper.x, toolsWrapper.y, "\nWidth, Height:\n" + toolsWrapper.width, toolsWrapper.height);
toolsBg.open = true;
}
}
@@ -287,7 +300,10 @@ Item {
anchors.margins: Tokens.padding.medium
Repeater {
model: root.current.pendingToolCalls ?? []
id: repeater
model: root.current.pendingToolCalls?.slice(0, 4) ?? []
delegate: RowLayout {
id: toolText
@@ -309,10 +325,10 @@ Item {
return String(seg.arguments);
}
CustomText {
MaterialIcon {
color: Colors.palette.m3onPrimaryContainer
text: String(toolText.index + 1)
font.pointSize: Tokens.font.size.large
text: toolText.modelData.name === "webfetch" ? "web" : "text_snippet"
fill: 1
}
CustomText {
@@ -331,12 +347,134 @@ Item {
}
}
}
CustomText {
color: Colors.palette.m3onPrimaryContainer
text: qsTr("And %1 more").arg(root.current.pendingToolCalls.slice(4).length)
visible: repeater.count < root.current.pendingToolCalls.length
}
}
}
Loader {
id: toolsContent
active: opacity > 0
anchors.fill: parent
asynchronous: false
opacity: 0
sourceComponent: MouseArea {
onWheel: event => event.accepted = true
preventStealing: true
implicitHeight: innerLayout.contentHeight + innerLayout.anchors.margins * 2
VerticalFadeListView {
id: innerLayout
anchors.fill: parent
anchors.margins: Tokens.padding.small
cacheBuffer: height * 10
fadeAmount: 0.1
fadeEnabled: interactive
spacing: 0
clip: true
interactive: contentHeight > toolsBg.maxHeight
model: root.current.pendingToolCalls ?? []
delegate: Item {
id: toolCall
required property LlmSegment modelData
property bool expanded: false
implicitWidth: CustomListView.view.width
implicitHeight: collapsedRow.implicitHeight + collapsedRow.anchors.margins + (expanded ? expandedTool.implicitHeight + expandedTool.anchors.margins + expandedTool.anchors.topMargin : collapsedRow.anchors.margins)
function toolTarget(seg: LlmSegment): string {
var a = null;
try {
a = JSON.parse(seg.arguments);
} catch (e) {}
if (a && typeof a === "object") {
const v = a.url ?? a.path;
if (v !== undefined && v !== null)
return String(v);
}
return String(seg.arguments);
}
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: collapsedRow
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Tokens.padding.medium
IconButton {
id: toolExpandBtn
font.pointSize: Tokens.font.size.large
icon: "keyboard_arrow_down"
inactiveOnColor: hovered ? Colors.palette.m3onSurface : Colors.palette.m3outline
rotation: toolCall.expanded ? 180 : 0
type: IconButton.Text
Behavior on rotation {
Anim {}
}
onClicked: toolCall.expanded = !toolCall.expanded
}
CustomText {
id: preview
Layout.fillWidth: true
text: toolCall.modelData.name
}
}
CustomClippingRect {
id: expandedTool
anchors.top: collapsedRow.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: Tokens.padding.medium
anchors.topMargin: Tokens.spacing.medium
implicitHeight: fullToolCall.implicitHeight + fullToolCall.anchors.margins * 2
radius: Tokens.rounding.medium
color: Colors.palette.m3surfaceContainerLow
CustomText {
id: fullToolCall
anchors.fill: parent
anchors.margins: Tokens.padding.medium
text: toolCall.toolTarget(toolCall.modelData)
wrapMode: CustomText.WrapAtWordBoundaryOrAnywhere
}
}
}
}
}
}
}
}
ButtonRow {
id: confirmationRow
Layout.alignment: Qt.AlignRight
spacing: Tokens.spacing.medium
@@ -366,7 +504,7 @@ Item {
}
}
CustomRect {
CustomClippingRect {
id: expandedRect
anchors.left: parent.left