merge main

This commit is contained in:
end-4
2026-04-05 23:20:10 +02:00
5 changed files with 107 additions and 54 deletions
@@ -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;
}
}
}
@@ -5,17 +5,9 @@ import qs.modules.common.utils
import qs.services import qs.services
import ".." import ".."
NestableObject { GCloudApi {
id: root id: root
enum State {
Done, Preparing, Processing
}
signal finished()
property var outputData
property var state: GCloudTranslate.State.Done
property list<string> pendingStrings property list<string> pendingStrings
property bool setupReady: false property bool setupReady: false
readonly property bool preparationReady: GoogleCloud.tokenReady && setupReady readonly property bool preparationReady: GoogleCloud.tokenReady && setupReady
@@ -24,13 +16,13 @@ NestableObject {
GoogleCloud.load(); GoogleCloud.load();
root.setupReady = false; root.setupReady = false;
root.pendingStrings = strings; root.pendingStrings = strings;
root.state = GCloudTranslate.State.Preparing; root.state = GCloudApi.State.Preparing;
root.setupReady = true; root.setupReady = true;
} }
onPreparationReadyChanged: { onPreparationReadyChanged: {
if (!preparationReady) return; if (!preparationReady) return;
root.state = GCloudTranslate.State.Processing; root.state = GCloudApi.State.Processing;
const targetLang = Translation.languageCode; const targetLang = Translation.languageCode;
const payload = { const payload = {
@@ -53,11 +45,7 @@ NestableObject {
]); ]);
seq.push(((out) => { seq.push(((out) => {
// print(out) root.handleApiOutput(out);
root.outputData = JSON.parse(out);
root.pendingStrings = [];
root.finished();
root.state = GCloudTranslate.State.Done;
})); }));
multiproc.runSequence(seq); multiproc.runSequence(seq);
@@ -7,18 +7,9 @@ import qs.services
import qs.modules.common import qs.modules.common
import ".." import ".."
NestableObject { GCloudApi {
id: root 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 imageBase64FilePath: `${Directories.screenshotTemp}/vision_base64.txt`
readonly property string payloadFilePath: `${Directories.screenshotTemp}/vision_payload.json` readonly property string payloadFilePath: `${Directories.screenshotTemp}/vision_payload.json`
property string uploadEndpoint: "https://uguu.se/upload" property string uploadEndpoint: "https://uguu.se/upload"
@@ -28,7 +19,8 @@ NestableObject {
readonly property bool preparationReady: tokenReady && onlineImageReady readonly property bool preparationReady: tokenReady && onlineImageReady
function annotateImage(imageUri: string) { function annotateImage(imageUri: string) {
root.state = GCloudVision.State.Uploading; resetState();
root.state = GCloudApi.State.Preparing;
root.onlineImageReady = false root.onlineImageReady = false
GoogleCloud.load(); GoogleCloud.load();
@@ -49,11 +41,11 @@ NestableObject {
onPreparationReadyChanged: { onPreparationReadyChanged: {
if (!preparationReady) return; if (!preparationReady) return;
if (GoogleCloud.tokenError || GoogleCloud.keyError) { if (GoogleCloud.tokenError || GoogleCloud.keyError) {
root.state = GCloudVision.State.Error; root.state = GCloudApi.State.Error;
root.error(); root.error(Translation.tr("Set your Google Cloud service account key"));
return; return;
} }
root.state = GCloudVision.State.Processing; root.state = GCloudApi.State.Processing;
var seq = []; // command sequence var seq = []; // command sequence
// Construct the JSON payload using jq to read from the base64 file // 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) => { seq.push((out) => {
root.outputData = JSON.parse(out); root.handleApiOutput(out);
root.finished();
root.state = GCloudVision.State.Done;
}); });
lookMultiproc.runSequence(seq); lookMultiproc.runSequence(seq);
@@ -34,6 +34,7 @@ Item {
} }
property bool error: false property bool error: false
property string errorMessage: ""
function showError() { function showError() {
error = true; error = true;
} }
@@ -45,13 +46,17 @@ Item {
cloudVision.annotateImage(screenshotPath); cloudVision.annotateImage(screenshotPath);
} }
function reattemptAsNeeded() {
if (root.visionParagraphs == [] && GoogleCloud.tokenReady && !GoogleCloud.tokenError) {
root.error = false;
cloudVision.annotateImage(root.screenshotPath);
}
}
Connections { Connections {
target: GoogleCloud target: GoogleCloud
function onTokenChanged() { function onTokenReadyChanged() {
if (GoogleCloud.tokenReady && !GoogleCloud.tokenError) { root.reattemptAsNeeded();
root.error = false;
cloudVision.annotateImage(root.screenshotPath);
}
} }
} }
@@ -76,15 +81,15 @@ Item {
StyledText { StyledText {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
text: { text: {
if (cloudVision.state == GCloudVision.State.Uploading) if (cloudVision.state == GCloudApi.State.Preparing)
return Translation.tr("Uploading image"); return Translation.tr("Uploading image");
else if (cloudVision.state == GCloudVision.State.Processing) else if (cloudVision.state == GCloudApi.State.Processing)
return Translation.tr("Reading image"); return Translation.tr("Reading image");
else if (cloudVision.state == GCloudVision.State.Error) else if (cloudVision.state == GCloudApi.State.Error)
return Translation.tr("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"); 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"); return Translation.tr("Translating");
else else
return " "; return " ";
@@ -111,19 +116,19 @@ Item {
} }
StyledText { StyledText {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
width: Math.min(root.windowWidth / 2, 800) * root.scaleFactor
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
textFormat: Text.MarkdownText 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 font.pixelSize: Appearance.font.pixelSize.small * root.scaleFactor
color: root.textColor color: root.textColor
MouseArea { onLinkActivated: (link) => {
anchors.fill: parent Qt.openUrlExternally(link)
cursorShape: Qt.PointingHandCursor GlobalStates.screenTranslatorOpen = false
onClicked: {
Qt.openUrlExternally(root.wikiLink)
GlobalStates.screenTranslatorOpen = false
}
} }
PointingHandLinkHover {}
} }
} }
} }
@@ -132,10 +137,16 @@ Item {
id: gcr 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 { GCloudVision {
id: cloudVision id: cloudVision
onError: { onError: (msg) => {
root.showError(); root.handleError(msg);
} }
onFinished: { onFinished: {
gcr.initializeWithData(outputData); gcr.initializeWithData(outputData);
@@ -149,6 +160,9 @@ Item {
GCloudTranslate { GCloudTranslate {
id: cloudTrans id: cloudTrans
onError: (msg) => {
root.handleError(msg);
}
onFinished: { onFinished: {
var values = outputData.translations.map(translation => translation.translatedText); var values = outputData.translations.map(translation => translation.translatedText);
const keys = root.translationKeys; const keys = root.translationKeys;
@@ -8,7 +8,7 @@ Singleton {
id: root id: root
property var keyContent: ({}) property var keyContent: ({})
property string keyProjectId: keyContent.project_id property string keyProjectId: keyContent?.project_id
property bool keyError: false property bool keyError: false
property bool keyReady: false property bool keyReady: false
property string token: "" 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 { function setKeyJson(str: string): bool {
try { try {
var keyData = JSON.parse(str) var keyData = JSON.parse(str)
root.unready();
KeyringStorage.setNestedField(["googleCloud", "serviceAccountKey"], keyData); KeyringStorage.setNestedField(["googleCloud", "serviceAccountKey"], keyData);
return true; return true;
} catch(e) { } catch(e) {
@@ -65,6 +73,7 @@ Singleton {
} catch(e) { } catch(e) {
root.tokenError = true; root.tokenError = true;
print("[GoogleCloud] Failed to parse token response: " + e) print("[GoogleCloud] Failed to parse token response: " + e)
print("[GoogleCloud] Failed to parse token response: " + e + "\n" + out)
} }
root.tokenReady = true; root.tokenReady = true;
} }