ai: gemini: annotation sources

This commit is contained in:
end-4
2025-05-10 10:56:35 +02:00
parent 0af7924be9
commit f93cca8a13
6 changed files with 130 additions and 2 deletions
@@ -9,6 +9,11 @@ function getDomain(url) {
return match ? match[1] : null;
}
function getBaseUrl(url) {
const match = url.match(/^(https?:\/\/[^\/]+)(\/.*)?$/);
return match ? match[1] : null;
}
function shellSingleQuoteEscape(str) {
// escape single quotes
return String(str)
@@ -20,10 +20,15 @@ Item {
property var inputField: messageInputField
readonly property var messages: Ai.messages
property string commandPrefix: "/"
property string faviconDownloadPath: StringUtils.trimFileProtocol(`${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/media/favicons`)
property var suggestionQuery: ""
property var suggestionList: []
Component.onCompleted: {
Hyprland.dispatch(`exec mkdir -p ${faviconDownloadPath}`)
}
Connections {
target: panelWindow
function onVisibleChanged(visible) {
@@ -205,6 +210,7 @@ int main(int argc, char* argv[]) {
messageIndex: index
messageData: modelData
messageInputField: root.inputField
faviconDownloadPath: root.faviconDownloadPath
}
}
@@ -20,6 +20,7 @@ Rectangle {
property int messageIndex
property var messageData
property var messageInputField
property string faviconDownloadPath
property real messagePadding: 7
property real contentSpacing: 3
@@ -74,7 +75,7 @@ Rectangle {
}
}
ColumnLayout {
ColumnLayout { // Main layout of the whole thing
id: columnLayout
anchors.left: parent.left
@@ -228,7 +229,7 @@ Rectangle {
}
}
ColumnLayout {
ColumnLayout { // Message content
id: messageContentColumnLayout
spacing: 0
@@ -257,6 +258,25 @@ Rectangle {
}
}
Flow { // Annotations
id: annotationFlowLayout
visible: root.messageData?.annotationSources?.length > 0
spacing: 5
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
Repeater {
model: root.messageData.annotationSources
delegate: AnnotationSourceButton {
id: annotationButton
faviconDownloadPath: root.faviconDownloadPath
displayText: modelData.text
url: modelData.url
}
}
}
}
}
@@ -0,0 +1,73 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import Qt.labs.platform
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Hyprland
Button {
id: root
property string displayText
property string url
implicitHeight: 30
leftPadding: 10
rightPadding: 10
property string downloadUserAgent: ConfigOptions.networking.userAgent
property string faviconDownloadPath
property string domainName: url.includes("vertexaisearch") ? displayText : StringUtils.getBaseUrl(url)
// property string faviconUrl: `https://${domainName}/favicon.ico`
property string faviconUrl: `https://www.google.com/s2/favicons?domain=${domainName}&sz=32`
property string fileName: `${domainName}.ico`
property string faviconFilePath: `${faviconDownloadPath}/${fileName}`
Process {
id: faviconDownloadProcess
running: false
command: ["bash", "-c", `[ -f ${faviconFilePath} ] || curl -s '${root.faviconUrl}' -o '${faviconFilePath}' -L -H 'User-Agent: ${downloadUserAgent}'`]
onExited: (exitCode, exitStatus) => {
root.faviconUrl = root.faviconFilePath
}
}
Component.onCompleted: {
console.log("Favicon download:", faviconDownloadProcess.command.join(" "))
faviconDownloadProcess.running = true
}
PointingHandInteraction {}
onClicked: {
if (url) {
Qt.openUrlExternally(url)
Hyprland.dispatch("global quickshell:sidebarLeftClose")
}
}
background: Rectangle {
radius: Appearance.rounding.full
color: (root.down ? Appearance.colors.colSurfaceContainerHighestActive :
root.hovered ? Appearance.colors.colSurfaceContainerHighestHover :
Appearance.m3colors.m3surfaceContainerHighest)
}
contentItem: RowLayout {
spacing: 5
IconImage {
id: iconImage
source: Qt.resolvedUrl(root.faviconUrl)
implicitSize: text.implicitHeight
}
StyledText {
id: text
horizontalAlignment: Text.AlignHCenter
text: displayText
color: Appearance.m3colors.m3onSurface
}
}
}
+22
View File
@@ -310,11 +310,33 @@ Singleton {
}
function parseGeminiBuffer() {
// console.log("BUFFER DATA: ", requester.geminiBuffer);
try {
const dataJson = JSON.parse(requester.geminiBuffer);
const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text
requester.message.content += responseContent;
const annotationSources = dataJson.candidates[0]?.groundingMetadata.groundingChunks?.map(chunk => {
return {
"type": "url_citation",
"text": chunk?.web?.title,
"url": chunk?.web?.uri,
}
});
const annotations = dataJson.candidates[0]?.groundingMetadata.groundingSupports?.map(citation => {
return {
"type": "url_citation",
"start_index": citation.segment?.startIndex,
"end_index": citation.segment?.endIndex,
"text": citation?.segment.text,
"url": annotationSources[citation.groundingChunkIndices[0]]?.url,
"sources": citation.groundingChunkIndices
}
});
requester.message.annotationSources = annotationSources;
requester.message.annotations = annotations;
// console.log(JSON.stringify(requester.message, null, 2));
} catch (e) {
console.log("[AI] Could not parse response from stream: ", e);
requester.message.content += requester.geminiBuffer
} finally {
requester.geminiBuffer = "";
@@ -7,4 +7,6 @@ QtObject {
property string model
property bool thinking: true
property bool done: false
property var annotations: []
property var annotationSources: []
}