ai: show search queries, temperature, and token count

This commit is contained in:
end-4
2025-07-24 18:05:21 +07:00
parent 7b8b388667
commit baa17c304b
5 changed files with 149 additions and 2 deletions
@@ -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
@@ -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
}
}
}
}
@@ -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
}
}
}
}
+17
View File
@@ -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);
@@ -13,6 +13,7 @@ QtObject {
property bool done: false
property var annotations: []
property var annotationSources: []
property list<string> searchQueries: []
property string functionName
property string functionCall
property string functionResponse