diff --git a/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml b/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml index 221eba9b4..262a88fa8 100644 --- a/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml @@ -188,10 +188,72 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\) } } + component StatusItem: MouseArea { + id: statusItem + property string icon + property string statusText + property string description + hoverEnabled: true + implicitHeight: statusItemRowLayout.implicitHeight + implicitWidth: statusItemRowLayout.implicitWidth + + RowLayout { + id: statusItemRowLayout + spacing: 4 + MaterialSymbol { + text: statusItem.icon + iconSize: Appearance.font.pixelSize.huge + color: Appearance.colors.colSubtext + } + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + text: statusItem.statusText + color: Appearance.colors.colSubtext + } + } + + StyledToolTip { + content: statusItem.description + extraVisibleCondition: false + alternativeVisibleCondition: statusItem.containsMouse + } + } + + component StatusSeparator: Rectangle { + implicitWidth: 4 + implicitHeight: 4 + radius: implicitWidth / 2 + color: Appearance.colors.colOutlineVariant + } + ColumnLayout { id: columnLayout anchors.fill: parent + RowLayout { // Status + Layout.alignment: Qt.AlignHCenter + spacing: 8 + + StatusItem { + icon: "device_thermostat" + statusText: Ai.temperature.toFixed(1) + description: Translation.tr("Temperature") + } + + StatusSeparator { + visible: Ai.tokenCount.total > 0 + } + + StatusItem { + visible: Ai.tokenCount.total > 0 + icon: "token" + statusText: Ai.tokenCount.total + description: Translation.tr("Total token count\nInput: %1\nOutput: %2") + .arg(Ai.tokenCount.input) + .arg(Ai.tokenCount.output) + } + } + Item { // Messages Layout.fillWidth: true Layout.fillHeight: true diff --git a/.config/quickshell/ii/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/ii/modules/sidebarLeft/aiChat/AiMessage.qml index 872c38ef4..d2b72d11c 100644 --- a/.config/quickshell/ii/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/ii/modules/sidebarLeft/aiChat/AiMessage.qml @@ -263,7 +263,6 @@ Rectangle { } Flow { // Annotations - id: annotationFlowLayout visible: root.messageData?.annotationSources?.length > 0 spacing: 5 Layout.fillWidth: true @@ -274,12 +273,28 @@ Rectangle { values: root.messageData?.annotationSources || [] } delegate: AnnotationSourceButton { - id: annotationButton + required property var modelData displayText: modelData.text url: modelData.url } } + } + Flow { // Search queries + visible: root.messageData?.searchQueries?.length > 0 + spacing: 5 + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft + + Repeater { + model: ScriptModel { + values: root.messageData?.searchQueries || [] + } + delegate: SearchQueryButton { + required property var modelData + query: modelData + } + } } } diff --git a/.config/quickshell/ii/modules/sidebarLeft/aiChat/SearchQueryButton.qml b/.config/quickshell/ii/modules/sidebarLeft/aiChat/SearchQueryButton.qml new file mode 100644 index 000000000..554a3cf69 --- /dev/null +++ b/.config/quickshell/ii/modules/sidebarLeft/aiChat/SearchQueryButton.qml @@ -0,0 +1,52 @@ +import qs.modules.common +import qs.modules.common.widgets +import qs.services +import qs.modules.common.functions +import QtQuick +import QtQuick.Layouts +import Quickshell.Hyprland + +RippleButton { + id: root + property string query + + implicitHeight: 30 + leftPadding: 6 + rightPadding: 10 + buttonRadius: Appearance.rounding.verysmall + colBackground: Appearance.colors.colSurfaceContainerHighest + colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover + colRipple: Appearance.colors.colSurfaceContainerHighestActive + + PointingHandInteraction {} + onClicked: { + let url = Config.options.search.engineBaseUrl + root.query; + for (let site of (Config?.options?.search.excludedSites ?? [])) { + url += ` -site:${site}`; + } + Qt.openUrlExternally(url); + Hyprland.dispatch("global quickshell:sidebarLeftClose") + } + + contentItem: Item { + anchors.centerIn: parent + implicitWidth: rowLayout.implicitWidth + implicitHeight: rowLayout.implicitHeight + RowLayout { + id: rowLayout + anchors.centerIn: parent + spacing: 5 + MaterialSymbol { + text: "search" + iconSize: 20 + color: Appearance.m3colors.m3onSurface + } + StyledText { + id: text + horizontalAlignment: Text.AlignHCenter + text: root.query + color: Appearance.m3colors.m3onSurface + } + } + } +} diff --git a/.config/quickshell/ii/services/Ai.qml b/.config/quickshell/ii/services/Ai.qml index 9f195c99d..78c56dbda 100644 --- a/.config/quickshell/ii/services/Ai.qml +++ b/.config/quickshell/ii/services/Ai.qml @@ -25,6 +25,11 @@ Singleton { readonly property var apiKeysLoaded: KeyringStorage.loaded property var postResponseHook property real temperature: Persistent.states?.ai?.temperature ?? 0.5 + property QtObject tokenCount: QtObject { + property int input: -1 + property int output: -1 + property int total: -1 + } function idForMessage(message) { // Generate a unique ID using timestamp and random value @@ -626,6 +631,7 @@ Singleton { root.handleGeminiFunctionCall(functionCall.name, functionCall.args); return } + // Normal text response const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text requester.message.rawContent += responseContent; @@ -638,6 +644,7 @@ Singleton { } }) ?? []; + // Handle annotations and search queries const annotations = dataJson.candidates[0]?.groundingMetadata?.groundingSupports?.map(citation => { return { "type": "url_citation", @@ -650,6 +657,16 @@ Singleton { }); requester.message.annotationSources = annotationSources; requester.message.annotations = annotations; + requester.message.searchQueries = dataJson.candidates[0]?.groundingMetadata?.webSearchQueries ?? []; + // console.log("[AI] Gemini: Search queries: ", JSON.stringify(requester.message.searchQueries, null, 2)); + + // Usage + root.tokenCount.input = dataJson.usageMetadata?.promptTokenCount ?? -1; + root.tokenCount.output = dataJson.usageMetadata?.candidatesTokenCount ?? -1; + root.tokenCount.total = dataJson.usageMetadata?.totalTokenCount ?? -1; + // console.log("[AI] Gemini: Token count: ", root.tokenCount); + + // Last logging // console.log(JSON.stringify(requester.message, null, 2)); } catch (e) { console.log("[AI] Gemini: Could not parse buffer: ", e); diff --git a/.config/quickshell/ii/services/AiMessageData.qml b/.config/quickshell/ii/services/AiMessageData.qml index c9537abe6..4695afe5f 100644 --- a/.config/quickshell/ii/services/AiMessageData.qml +++ b/.config/quickshell/ii/services/AiMessageData.qml @@ -13,6 +13,7 @@ QtObject { property bool done: false property var annotations: [] property var annotationSources: [] + property list searchQueries: [] property string functionName property string functionCall property string functionResponse