forked from Shinonome/dots-hyprland
ai: gemini: files
This commit is contained in:
@@ -40,7 +40,6 @@ Singleton {
|
||||
* @returns { string }
|
||||
*/
|
||||
function shellSingleQuoteEscape(str) {
|
||||
// escape single quotes
|
||||
return String(str)
|
||||
// .replace(/\\/g, '\\\\')
|
||||
.replace(/'/g, "'\\''");
|
||||
|
||||
@@ -38,6 +38,13 @@ Item {
|
||||
}
|
||||
|
||||
property var allCommands: [
|
||||
{
|
||||
name: "attach",
|
||||
description: Translation.tr("Attach a file. Only works with Gemini."),
|
||||
execute: (args) => {
|
||||
Ai.attachFile(args.join(" ").trim());
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "model",
|
||||
description: Translation.tr("Choose model"),
|
||||
@@ -421,13 +428,13 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
|
||||
Rectangle { // Input area
|
||||
id: inputWrapper
|
||||
property real columnSpacing: 5
|
||||
property real spacing: 5
|
||||
Layout.fillWidth: true
|
||||
radius: Appearance.rounding.small
|
||||
color: Appearance.colors.colLayer1
|
||||
implicitWidth: messageInputField.implicitWidth
|
||||
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
|
||||
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45)
|
||||
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
|
||||
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + spacing, 45)
|
||||
+ (attachedFileIndicator.implicitHeight + spacing + attachedFileIndicator.anchors.topMargin)
|
||||
clip: true
|
||||
border.color: Appearance.colors.colOutlineVariant
|
||||
border.width: 1
|
||||
@@ -436,12 +443,26 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
AttachedFileIndicator {
|
||||
id: attachedFileIndicator
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: visible ? 5 : 0
|
||||
}
|
||||
filePath: Ai.pendingFilePath
|
||||
onRemove: Ai.attachFile("")
|
||||
}
|
||||
|
||||
RowLayout { // Input field and send button
|
||||
id: inputFieldRowLayout
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 5
|
||||
anchors {
|
||||
top: attachedFileIndicator.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
topMargin: 5
|
||||
}
|
||||
spacing: 0
|
||||
|
||||
StyledTextArea { // The actual TextArea
|
||||
|
||||
@@ -233,6 +233,15 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
active: root.messageData?.localFilePath && root.messageData?.localFilePath.length > 0
|
||||
sourceComponent: AttachedFileIndicator {
|
||||
filePath: root.messageData?.localFilePath
|
||||
canRemove: false
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout { // Message content
|
||||
id: messageContentColumnLayout
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
import qs.modules.common.functions
|
||||
import qs.services
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Hyprland
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Quickshell.Io
|
||||
import qs
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
signal remove()
|
||||
property bool canRemove: true
|
||||
property string filePath: ""
|
||||
property string mimeType: ""
|
||||
property real maxHeight: 200
|
||||
property real imageWidth: -1
|
||||
property real imageHeight: -1
|
||||
property real scale: Math.min(root.maxHeight / imageHeight, root.width / imageWidth)
|
||||
onFilePathChanged: refresh()
|
||||
visible: filePath !== ""
|
||||
|
||||
function refresh() {
|
||||
root.mimeType = "";
|
||||
root.imageWidth = -1;
|
||||
root.imageHeight = -1;
|
||||
fileTypeProc.exec(["file", "-b", "--mime-type", filePath]);
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fileTypeProc
|
||||
command: ["file", "-b", "--mime-type", filePath]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
root.mimeType = this.text;
|
||||
if (root.mimeType.startsWith("image/"))
|
||||
imageSizeProc.exec(["identify", "-format", "%wx%h", filePath]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: imageSizeProc
|
||||
command: ["identify", "-format", "%wx%h", filePath]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const dimensions = this.text.split("x");
|
||||
root.imageWidth = parseInt(dimensions[0]);
|
||||
root.imageHeight = parseInt(dimensions[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Styles/widgets
|
||||
property real horizontalPadding: 10
|
||||
property real verticalPadding: 10
|
||||
radius: Appearance.rounding.small - anchors.margins
|
||||
color: Appearance.colors.colLayer2
|
||||
implicitHeight: visible ? (contentItem.implicitHeight + verticalPadding * 2) : 0
|
||||
|
||||
ColumnLayout {
|
||||
id: contentItem
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: root.horizontalPadding
|
||||
rightMargin: root.horizontalPadding
|
||||
topMargin: root.verticalPadding
|
||||
bottomMargin: root.verticalPadding
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
MaterialSymbol {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
text: {
|
||||
if (root.mimeType.startsWith("image/"))
|
||||
return "image";
|
||||
if (root.mimeType.startsWith("audio/"))
|
||||
return "music_note";
|
||||
if (root.mimeType.startsWith("video/"))
|
||||
return "movie";
|
||||
if (root.mimeType === "application/pdf")
|
||||
return "picture_as_pdf";
|
||||
if (root.mimeType.startsWith("text/"))
|
||||
return "description";
|
||||
return "file_present";
|
||||
}
|
||||
iconSize: Appearance.font.pixelSize.hugeass
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 4
|
||||
text: root.filePath
|
||||
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||
font.family: Appearance.font.family.monospace
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
RippleButton {
|
||||
visible: root.canRemove
|
||||
Layout.alignment: Qt.AlignTop
|
||||
buttonRadius: Appearance.rounding.full
|
||||
colBackground: Appearance.colors.colLayer2
|
||||
implicitHeight: 28
|
||||
implicitWidth: 28
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
text: "close"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
color: Appearance.colors.colOnSurfaceVariant
|
||||
}
|
||||
|
||||
onClicked: root.remove()
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: imagePreviewLoader
|
||||
visible: (root.imageWidth != -1) && (root.imageHeight != -1)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
sourceComponent: Item {
|
||||
implicitHeight: root.imageHeight * root.scale
|
||||
implicitWidth: imagePreview.implicitWidth
|
||||
Image {
|
||||
id: imagePreview
|
||||
anchors.fill: parent
|
||||
source: Qt.resolvedUrl(root.filePath)
|
||||
fillMode: Image.PreserveAspectFit
|
||||
antialiasing: true
|
||||
asynchronous: true
|
||||
width: root.imageWidth * root.scale
|
||||
height: root.imageHeight * root.scale
|
||||
sourceSize.width: root.imageWidth * root.scale
|
||||
sourceSize.height: root.imageHeight * root.scale
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: imagePreview.width
|
||||
height: imagePreview.height
|
||||
radius: Appearance.rounding.normal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user