C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Failing after 33s
Rust / build (pull_request) Successful in 48s
Python / verify (pull_request) Failing after 1m24s
Rust / fmt (pull_request) Successful in 32s
Rust / clippy (pull_request) Successful in 57s
409 lines
11 KiB
QML
409 lines
11 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
import ZShell.Config
|
|
import ZShell.Components
|
|
import ZShell.Llm
|
|
import ZShell.Blobs
|
|
import qs.Components
|
|
import qs.Services
|
|
|
|
Item {
|
|
id: root
|
|
|
|
required property Item rootParent
|
|
required property ChatGeneration current
|
|
|
|
implicitHeight: approvalLayout.implicitHeight + approvalLayout.anchors.margins * 2
|
|
|
|
CustomRect {
|
|
id: approvalBg
|
|
|
|
anchors.fill: parent
|
|
radius: Tokens.rounding.medium
|
|
color: Colors.palette.m3surface
|
|
border.width: 1
|
|
border.color: Colors.palette.m3outlineVariant
|
|
|
|
ColumnLayout {
|
|
id: approvalLayout
|
|
anchors.fill: parent
|
|
anchors.margins: Tokens.padding.medium
|
|
spacing: Tokens.spacing.small
|
|
|
|
CustomText {
|
|
id: requestHeader
|
|
text: qsTr("Tool call request")
|
|
font: Tokens.font.headline.medium
|
|
color: Colors.palette.m3onSurface
|
|
}
|
|
|
|
Item {
|
|
id: toolsBg
|
|
|
|
property bool open: false
|
|
property int maxHeight: root.rootParent.height - Tokens.padding.medium * 2
|
|
property int openHeight: Math.min(toolsContent.item?.implicitHeight, maxHeight)
|
|
property int openWidth: root.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 ? root.rootParent : toolsBg;
|
|
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 = c.x - toolsWrapper.width / 2;
|
|
toolsWrapper.y = c.y - toolsWrapper.height / 2;
|
|
}
|
|
|
|
onOpenChanged: {
|
|
if (open)
|
|
root.rootParent.interactive = false;
|
|
else
|
|
root.rootParent.interactive = true;
|
|
}
|
|
|
|
BlobGroup {
|
|
id: blobGroup
|
|
|
|
color: toolsBg.open ? Colors.palette.m3surfaceContainerHighest : Colors.tPalette.m3primaryContainer
|
|
|
|
Behavior on color {
|
|
CAnim {}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: backdrop
|
|
|
|
anchors.fill: parent
|
|
enabled: false
|
|
hoverEnabled: enabled
|
|
preventStealing: true
|
|
parent: toolsBg.open ? root.rootParent : toolsBg
|
|
|
|
onClicked: toolsBg.open = false
|
|
}
|
|
|
|
Item {
|
|
id: toolsWrapper
|
|
|
|
width: toolsBg.width
|
|
height: toolBox.implicitHeight
|
|
rotation: parent === root.rootParent ? 180 : 0
|
|
z: 1
|
|
|
|
states: State {
|
|
name: "open"
|
|
when: toolsBg.open
|
|
|
|
PropertyChanges {
|
|
backdrop.enabled: true
|
|
toolsBackground.bottomLeftRadius: Tokens.rounding.largeIncreased
|
|
toolsBackground.bottomRightRadius: Tokens.rounding.largeIncreased
|
|
toolsBackground.topRightRadius: Tokens.rounding.largeIncreased
|
|
toolsBackground.topLeftRadius: Tokens.rounding.largeIncreased
|
|
toolsContent.opacity: 1
|
|
toolsWrapper.height: toolsBg.openHeight
|
|
toolsWrapper.width: toolsBg.openWidth
|
|
toolsWrapper.x: (root.rootParent.width - toolsBg.openWidth) / 2
|
|
toolsWrapper.y: (confirmationRow.implicitHeight + Tokens.spacing.small + Tokens.padding.medium) * (1 - toolsBg.openHeightProg) + Tokens.padding.medium
|
|
elevation.opacity: 1
|
|
toolBox.opacity: 0
|
|
}
|
|
}
|
|
transitions: Transition {
|
|
id: dialogTransition
|
|
|
|
SequentialAnimation {
|
|
ScriptAction {
|
|
script: toolsBg.reparentWrapper()
|
|
}
|
|
|
|
Anim {
|
|
properties: "x,y"
|
|
}
|
|
}
|
|
|
|
PropertyAction {
|
|
property: "enabled"
|
|
}
|
|
|
|
Anim {
|
|
properties: "opacity,topLeftRadius,topRightRadius,bottomLeftRadius,bottomRightRadius"
|
|
type: Anim.DefaultEffects
|
|
}
|
|
|
|
Anim {
|
|
properties: "width,height"
|
|
}
|
|
}
|
|
|
|
Elevation {
|
|
id: elevation
|
|
|
|
anchors.fill: parent
|
|
bottomLeftRadius: toolsBackground.bottomLeftRadius
|
|
bottomRightRadius: toolsBackground.bottomRightRadius
|
|
level: 4
|
|
opacity: 0
|
|
radius: toolsBackground.radius
|
|
|
|
transform: Matrix4x4 {
|
|
matrix: toolsBackground.deformMatrix
|
|
}
|
|
}
|
|
|
|
BlobRect {
|
|
id: toolsBackground
|
|
|
|
anchors.fill: parent
|
|
bottomLeftRadius: toolsBg.open ? Tokens.rounding.largeIncreased : Tokens.rounding.small
|
|
bottomRightRadius: toolsBg.open ? Tokens.rounding.largeIncreased : Tokens.rounding.small
|
|
topRightRadius: toolsBg.open ? Tokens.rounding.largeIncreased : Tokens.rounding.small
|
|
topLeftRadius: toolsBg.open ? Tokens.rounding.largeIncreased : Tokens.rounding.small
|
|
deformScale: 0
|
|
group: blobGroup
|
|
opacity: blobGroup.color.a * (root.enabled ? 1 : 0.5)
|
|
}
|
|
|
|
CustomRect {
|
|
id: toolBox
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
height: Math.min(implicitHeight, parent.height)
|
|
implicitHeight: toolList.implicitHeight + Tokens.padding.medium * 2
|
|
radius: Tokens.rounding.small
|
|
|
|
StateLayer {
|
|
onClicked: {
|
|
toolsBg.open = true;
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
id: toolList
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.margins: Tokens.padding.medium
|
|
|
|
Repeater {
|
|
id: repeater
|
|
|
|
model: root.current.pendingToolCalls?.slice(0, 4) ?? []
|
|
|
|
delegate: RowLayout {
|
|
id: toolText
|
|
|
|
required property var modelData
|
|
required property int index
|
|
|
|
spacing: Tokens.spacing.medium
|
|
|
|
function toolTarget(seg: var): 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);
|
|
}
|
|
|
|
MaterialIcon {
|
|
color: Colors.palette.m3onPrimaryContainer
|
|
text: toolText.modelData.name === "webfetch" ? "web" : "text_snippet"
|
|
fill: 1
|
|
}
|
|
|
|
CustomText {
|
|
color: Colors.palette.m3onPrimaryContainer
|
|
text: qsTr("%1").arg(toolText.modelData.name)
|
|
}
|
|
|
|
MaterialIcon {
|
|
color: Colors.palette.m3onPrimaryContainer
|
|
text: "arrow_right_alt"
|
|
}
|
|
|
|
CustomText {
|
|
color: Colors.palette.m3onPrimaryContainer
|
|
text: qsTr("%1").arg(toolText.toolTarget(toolText.modelData))
|
|
}
|
|
}
|
|
}
|
|
|
|
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 var 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: var): 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: Tokens.font.icon.medium
|
|
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
|
|
}
|
|
}
|
|
|
|
Item {
|
|
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
|
|
clip: true
|
|
|
|
CustomRect {
|
|
id: bg
|
|
|
|
anchors.fill: parent
|
|
|
|
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
|
|
|
|
IconTextButton {
|
|
icon: "check_circle"
|
|
text: qsTr("Allow")
|
|
horizontalPadding: Tokens.padding.large
|
|
verticalPadding: Tokens.padding.small
|
|
isRound: true
|
|
shapeMorph: true
|
|
onClicked: root.current.approveTools()
|
|
}
|
|
|
|
IconTextButton {
|
|
icon: "cancel"
|
|
text: qsTr("Deny")
|
|
isRound: true
|
|
shapeMorph: true
|
|
inactiveColor: Colors.palette.m3error
|
|
inactiveOnColor: Colors.palette.m3onError
|
|
horizontalPadding: Tokens.padding.large
|
|
verticalPadding: Tokens.padding.small
|
|
onClicked: root.current.denyTools()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|