diff --git a/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudApi.qml b/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudApi.qml new file mode 100644 index 000000000..a314909fd --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudApi.qml @@ -0,0 +1,52 @@ +pragma ComponentBehavior: Bound +import QtQuick +import Quickshell +import qs.modules.common.functions +import qs.modules.common.utils +import qs.services +import qs.modules.common +import ".." + +NestableObject { + id: root + + enum State { + Done, Preparing, Processing, Error + } + + signal finished() + signal error(message: string) + property int errorCode + property string errorMessage: "" + property var outputData + property var state: GCloudApi.State.Done + + function resetState() { + root.state = GCloudApi.State.Done; + root.errorMessage = ""; + root.outputData = undefined; + } + + function handleApiOutput(out: string): bool { + try { + root.outputData = JSON.parse(out); + if (outputData.error) { + print("API error: " + JSON.stringify(outputData.error, null, 2)) + root.state = GCloudApi.State.Error; + root.errorCode = outputData.error.code; + root.errorMessage = outputData.error.message; + root.error(outputData.error.message); + return false; + } + root.finished(); + root.state = GCloudApi.State.Done; + return true + } catch (e) { + print("Failed to parse API response: " + e + "\n" + out) + root.state = GCloudApi.State.Error; + root.errorMessage = "Failed to parse API response"; + root.error(root.errorMessage); + return false; + } + } +} diff --git a/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudTranslate.qml b/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudTranslate.qml index d117e8d3a..01604ac0a 100644 --- a/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudTranslate.qml +++ b/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudTranslate.qml @@ -5,17 +5,9 @@ import qs.modules.common.utils import qs.services import ".." -NestableObject { +GCloudApi { id: root - enum State { - Done, Preparing, Processing - } - - signal finished() - property var outputData - property var state: GCloudTranslate.State.Done - property list pendingStrings property bool setupReady: false readonly property bool preparationReady: GoogleCloud.tokenReady && setupReady @@ -24,13 +16,13 @@ NestableObject { GoogleCloud.load(); root.setupReady = false; root.pendingStrings = strings; - root.state = GCloudTranslate.State.Preparing; + root.state = GCloudApi.State.Preparing; root.setupReady = true; } onPreparationReadyChanged: { if (!preparationReady) return; - root.state = GCloudTranslate.State.Processing; + root.state = GCloudApi.State.Processing; const targetLang = Translation.languageCode; const payload = { @@ -53,11 +45,7 @@ NestableObject { ]); seq.push(((out) => { - // print(out) - root.outputData = JSON.parse(out); - root.pendingStrings = []; - root.finished(); - root.state = GCloudTranslate.State.Done; + root.handleApiOutput(out); })); multiproc.runSequence(seq); diff --git a/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudVision.qml b/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudVision.qml index 3e02fd561..0f5553c7f 100644 --- a/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudVision.qml +++ b/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudVision.qml @@ -7,18 +7,9 @@ import qs.services import qs.modules.common import ".." -NestableObject { +GCloudApi { id: root - enum State { - Done, Uploading, Processing, Error - } - - signal finished() - signal error() - property var outputData - property var state: GCloudVision.State.Done - readonly property string imageBase64FilePath: `${Directories.screenshotTemp}/vision_base64.txt` readonly property string payloadFilePath: `${Directories.screenshotTemp}/vision_payload.json` property string uploadEndpoint: "https://uguu.se/upload" @@ -28,7 +19,8 @@ NestableObject { readonly property bool preparationReady: tokenReady && onlineImageReady function annotateImage(imageUri: string) { - root.state = GCloudVision.State.Uploading; + resetState(); + root.state = GCloudApi.State.Preparing; root.onlineImageReady = false GoogleCloud.load(); @@ -49,11 +41,11 @@ NestableObject { onPreparationReadyChanged: { if (!preparationReady) return; if (GoogleCloud.tokenError || GoogleCloud.keyError) { - root.state = GCloudVision.State.Error; - root.error(); + root.state = GCloudApi.State.Error; + root.error(Translation.tr("Set your Google Cloud service account key")); return; } - root.state = GCloudVision.State.Processing; + root.state = GCloudApi.State.Processing; var seq = []; // command sequence // Construct the JSON payload using jq to read from the base64 file @@ -75,9 +67,7 @@ https://vision.googleapis.com/v1/images:annotate \ ]); seq.push((out) => { - root.outputData = JSON.parse(out); - root.finished(); - root.state = GCloudVision.State.Done; + root.handleApiOutput(out); }); lookMultiproc.runSequence(seq); diff --git a/dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTextOverlay.qml b/dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTextOverlay.qml index cd1c6a202..8236fb189 100644 --- a/dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTextOverlay.qml +++ b/dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTextOverlay.qml @@ -34,6 +34,7 @@ Item { } property bool error: false + property string errorMessage: "" function showError() { error = true; } @@ -45,13 +46,17 @@ Item { cloudVision.annotateImage(screenshotPath); } + function reattemptAsNeeded() { + if (root.visionParagraphs == [] && GoogleCloud.tokenReady && !GoogleCloud.tokenError) { + root.error = false; + cloudVision.annotateImage(root.screenshotPath); + } + } + Connections { target: GoogleCloud - function onTokenChanged() { - if (GoogleCloud.tokenReady && !GoogleCloud.tokenError) { - root.error = false; - cloudVision.annotateImage(root.screenshotPath); - } + function onTokenReadyChanged() { + root.reattemptAsNeeded(); } } @@ -76,15 +81,15 @@ Item { StyledText { anchors.horizontalCenter: parent.horizontalCenter text: { - if (cloudVision.state == GCloudVision.State.Uploading) + if (cloudVision.state == GCloudApi.State.Preparing) return Translation.tr("Uploading image"); - else if (cloudVision.state == GCloudVision.State.Processing) + else if (cloudVision.state == GCloudApi.State.Processing) return Translation.tr("Reading image"); - else if (cloudVision.state == GCloudVision.State.Error) + else if (cloudVision.state == GCloudApi.State.Error) return Translation.tr("Error"); - else if (cloudTrans.state == GCloudTranslate.State.Preparing) + else if (cloudTrans.state == GCloudApi.State.Preparing) return Translation.tr("Getting ready to translate"); - else if (cloudTrans.state == GCloudTranslate.State.Processing) + else if (cloudTrans.state == GCloudApi.State.Processing) return Translation.tr("Translating"); else return " "; @@ -111,19 +116,19 @@ Item { } StyledText { anchors.horizontalCenter: parent.horizontalCenter + width: Math.min(root.windowWidth / 2, 800) * root.scaleFactor horizontalAlignment: Text.AlignHCenter textFormat: Text.MarkdownText - text: `**${Translation.tr("Screen Translator")}**\n\n${Translation.tr("Set your Google Cloud service account key")}\n\n__[${Translation.tr("See how on the wiki")}](${root.wikiLink})__` + wrapMode: Text.Wrap + text: `**${Translation.tr("Screen Translator")}**\n\n${root.errorMessage}\n\n__[${Translation.tr("See setup instructions on the wiki")}](${root.wikiLink})__` font.pixelSize: Appearance.font.pixelSize.small * root.scaleFactor color: root.textColor - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - Qt.openUrlExternally(root.wikiLink) - GlobalStates.screenTranslatorOpen = false - } + onLinkActivated: (link) => { + Qt.openUrlExternally(link) + GlobalStates.screenTranslatorOpen = false } + + PointingHandLinkHover {} } } } @@ -132,10 +137,16 @@ Item { id: gcr } + function handleError(msg) { + if (msg?.length > 0) root.errorMessage = msg; + else root.errorMessage = Translation.tr("Set your Google Cloud service account key"); + root.showError(); + } + GCloudVision { id: cloudVision - onError: { - root.showError(); + onError: (msg) => { + root.handleError(msg); } onFinished: { gcr.initializeWithData(outputData); @@ -149,6 +160,9 @@ Item { GCloudTranslate { id: cloudTrans + onError: (msg) => { + root.handleError(msg); + } onFinished: { var values = outputData.translations.map(translation => translation.translatedText); const keys = root.translationKeys; diff --git a/dots/.config/quickshell/ii/services/GoogleCloud.qml b/dots/.config/quickshell/ii/services/GoogleCloud.qml index d357ebde1..cd2ae3675 100644 --- a/dots/.config/quickshell/ii/services/GoogleCloud.qml +++ b/dots/.config/quickshell/ii/services/GoogleCloud.qml @@ -8,7 +8,7 @@ Singleton { id: root property var keyContent: ({}) - property string keyProjectId: keyContent.project_id + property string keyProjectId: keyContent?.project_id property bool keyError: false property bool keyReady: false property string token: "" @@ -32,9 +32,17 @@ Singleton { } } + function unready() { + root.keyReady = false; + root.tokenReady = false; + root.keyError = false; + root.tokenError = false; + } + function setKeyJson(str: string): bool { try { var keyData = JSON.parse(str) + root.unready(); KeyringStorage.setNestedField(["googleCloud", "serviceAccountKey"], keyData); return true; } catch(e) { @@ -65,6 +73,7 @@ Singleton { } catch(e) { root.tokenError = true; print("[GoogleCloud] Failed to parse token response: " + e) + print("[GoogleCloud] Failed to parse token response: " + e + "\n" + out) } root.tokenReady = true; }