forked from Shinonome/dots-hyprland
ai: gemini: annotation sources
This commit is contained in:
@@ -9,6 +9,11 @@ function getDomain(url) {
|
|||||||
return match ? match[1] : null;
|
return match ? match[1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBaseUrl(url) {
|
||||||
|
const match = url.match(/^(https?:\/\/[^\/]+)(\/.*)?$/);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
function shellSingleQuoteEscape(str) {
|
function shellSingleQuoteEscape(str) {
|
||||||
// escape single quotes
|
// escape single quotes
|
||||||
return String(str)
|
return String(str)
|
||||||
|
|||||||
@@ -20,10 +20,15 @@ Item {
|
|||||||
property var inputField: messageInputField
|
property var inputField: messageInputField
|
||||||
readonly property var messages: Ai.messages
|
readonly property var messages: Ai.messages
|
||||||
property string commandPrefix: "/"
|
property string commandPrefix: "/"
|
||||||
|
property string faviconDownloadPath: StringUtils.trimFileProtocol(`${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/media/favicons`)
|
||||||
|
|
||||||
property var suggestionQuery: ""
|
property var suggestionQuery: ""
|
||||||
property var suggestionList: []
|
property var suggestionList: []
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
Hyprland.dispatch(`exec mkdir -p ${faviconDownloadPath}`)
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: panelWindow
|
target: panelWindow
|
||||||
function onVisibleChanged(visible) {
|
function onVisibleChanged(visible) {
|
||||||
@@ -205,6 +210,7 @@ int main(int argc, char* argv[]) {
|
|||||||
messageIndex: index
|
messageIndex: index
|
||||||
messageData: modelData
|
messageData: modelData
|
||||||
messageInputField: root.inputField
|
messageInputField: root.inputField
|
||||||
|
faviconDownloadPath: root.faviconDownloadPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ Rectangle {
|
|||||||
property int messageIndex
|
property int messageIndex
|
||||||
property var messageData
|
property var messageData
|
||||||
property var messageInputField
|
property var messageInputField
|
||||||
|
property string faviconDownloadPath
|
||||||
|
|
||||||
property real messagePadding: 7
|
property real messagePadding: 7
|
||||||
property real contentSpacing: 3
|
property real contentSpacing: 3
|
||||||
@@ -74,7 +75,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout { // Main layout of the whole thing
|
||||||
id: columnLayout
|
id: columnLayout
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -228,7 +229,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout { // Message content
|
||||||
id: messageContentColumnLayout
|
id: messageContentColumnLayout
|
||||||
|
|
||||||
spacing: 0
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -310,11 +310,33 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseGeminiBuffer() {
|
function parseGeminiBuffer() {
|
||||||
|
// console.log("BUFFER DATA: ", requester.geminiBuffer);
|
||||||
try {
|
try {
|
||||||
const dataJson = JSON.parse(requester.geminiBuffer);
|
const dataJson = JSON.parse(requester.geminiBuffer);
|
||||||
const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text
|
const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text
|
||||||
requester.message.content += responseContent;
|
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) {
|
} catch (e) {
|
||||||
|
console.log("[AI] Could not parse response from stream: ", e);
|
||||||
requester.message.content += requester.geminiBuffer
|
requester.message.content += requester.geminiBuffer
|
||||||
} finally {
|
} finally {
|
||||||
requester.geminiBuffer = "";
|
requester.geminiBuffer = "";
|
||||||
|
|||||||
@@ -7,4 +7,6 @@ QtObject {
|
|||||||
property string model
|
property string model
|
||||||
property bool thinking: true
|
property bool thinking: true
|
||||||
property bool done: false
|
property bool done: false
|
||||||
|
property var annotations: []
|
||||||
|
property var annotationSources: []
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user