/* Antigravity Usage — dashboard desktop per KDE Plasma 6. */ import QtQuick import QtQuick.Layouts import QtQuick.Controls as QQC2 import org.kde.plasma.plasmoid import org.kde.plasma.core as PlasmaCore import org.kde.plasma.components 3.0 as PC3 import org.kde.plasma.plasma5support as Plasma5Support import org.kde.kirigami as Kirigami PlasmoidItem { id: root property var data: null property string lastUpdate: "" property string errorMsg: "" property bool loading: false property int clockTick: 0 property int refreshSecs: Plasmoid.configuration.refreshIntervalSec property int warnThreshold: Plasmoid.configuration.warnThreshold property int critThreshold: Plasmoid.configuration.critThreshold readonly property color themeViolet: "#a855f7" readonly property color themeIndigo: "#818cf8" readonly property color themeCyan: "#38bdf8" readonly property color themeGreen: "#4ade80" Plasmoid.title: i18n("Antigravity") Plasmoid.icon: "compass" Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground toolTipMainText: i18n("Antigravity — quote modelli") toolTipSubText: root.data && root.data.gemini ? i18n("Gemini: %1% · Claude: %2% · Finestra mobile (rolling)", Math.round((root.data.gemini.remaining || 0) * 100), Math.round((root.data.claude ? root.data.claude.remaining || 0 : 0) * 100)) : (root.errorMsg || "") preferredRepresentation: fullRepresentation switchWidth: 150 switchHeight: 150 Plasma5Support.DataSource { id: executable engine: "executable" connectedSources: [] onNewData: function(sourceName, result) { root.loading = false executable.disconnectSource(sourceName) if (result["exit code"] !== 0) { root.errorMsg = i18n("comando fallito (exit %1)", result["exit code"]) return } try { const stdout = (result["stdout"] || "").trim() const parsed = JSON.parse(stdout) if (parsed && parsed.ok === false) { root.errorMsg = parsed.error + (parsed.detail ? " — " + parsed.detail : "") root.data = null } else if (parsed && parsed.ok) { root.data = parsed root.errorMsg = "" root.lastUpdate = new Date().toLocaleTimeString(Qt.locale(), "HH:mm:ss") } else { root.errorMsg = i18n("risposta inattesa") root.data = null } } catch (e) { root.errorMsg = i18n("JSON non valido: %1", e.message) root.data = null } } } function refresh() { if (root.loading) return root.loading = true const path = Qt.resolvedUrl("../scripts/usage.sh").toString().replace(/^file:\/\//, "") executable.connectSource("/bin/bash " + shellQuote(path)) } function shellQuote(s) { return "'" + String(s).replace(/'/g, "'\"'\"'") + "'" } function accentColorFor(pct) { if (pct <= root.critThreshold) return Kirigami.Theme.negativeTextColor if (pct <= root.warnThreshold) return Kirigami.Theme.neutralTextColor return root.themeIndigo } Component.onCompleted: root.refresh() Timer { interval: Math.max(30, root.refreshSecs) * 1000 repeat: true running: true onTriggered: root.refresh() } Timer { interval: 30000 repeat: true running: true onTriggered: root.clockTick += 1 } compactRepresentation: Component { Item { implicitWidth: Kirigami.Units.iconSizes.smallMedium implicitHeight: Kirigami.Units.iconSizes.smallMedium Kirigami.Icon { anchors.fill: parent source: Plasmoid.icon color: root.data ? root.themeIndigo : Kirigami.Theme.disabledTextColor } MouseArea { anchors.fill: parent onClicked: Plasmoid.expanded = !Plasmoid.expanded } } } fullRepresentation: Component { Item { implicitWidth: 224 implicitHeight: 224 Layout.preferredWidth: 224 Layout.preferredHeight: 224 ColumnLayout { anchors.fill: parent anchors.margins: 8 spacing: 4 // Header RowLayout { Layout.fillWidth: true spacing: 6 Kirigami.Icon { source: Plasmoid.icon color: root.themeViolet Layout.preferredWidth: 20 Layout.preferredHeight: 20 } ColumnLayout { spacing: 0 PC3.Label { text: i18n("ANTIGRAVITY") font.bold: true font.pointSize: Kirigami.Theme.defaultFont.pointSize + 1 } PC3.Label { text: "CLOUDCODE-PA" color: root.themeViolet font.bold: true font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 } } Item { Layout.fillWidth: true } Rectangle { Layout.preferredWidth: 8 Layout.preferredHeight: 8 radius: 4 color: root.errorMsg ? Kirigami.Theme.negativeTextColor : root.themeGreen } PC3.Button { icon.name: "view-refresh" display: QQC2.AbstractButton.IconOnly enabled: !root.loading onClicked: root.refresh() } } // Separatore Rectangle { Layout.fillWidth: true Layout.preferredHeight: 1 color: Qt.rgba(0.66, 0.33, 0.97, 0.35) } // Messaggio Errore PC3.Label { visible: root.errorMsg.length > 0 Layout.fillWidth: true wrapMode: Text.Wrap color: Kirigami.Theme.negativeTextColor text: i18n("Errore: %1", root.errorMsg) } // 1. Gemini Card GroupCard { tag: "GEMINI" title: "Gemini 3.x" models: root.data && root.data.gemini ? (root.data.gemini.models || "Pro/Flash") : "Pro/Flash" remaining: root.data && root.data.gemini ? (root.data.gemini.remaining || 0) : 1.0 usedPercent: root.data && root.data.gemini ? (root.data.gemini.used_percent || 0) : 0 resetAt: root.data && root.data.gemini ? (root.data.gemini.reset_at || 0) : 0 clockTick: root.clockTick accentColor: root.accentColorFor(Math.round((root.data && root.data.gemini ? root.data.gemini.remaining : 1.0) * 100)) } // 2. Claude Card GroupCard { tag: "CLAUDE" title: "Claude 4.6" models: root.data && root.data.claude ? (root.data.claude.models || "Sonnet/Opus") : "Sonnet/Opus" remaining: root.data && root.data.claude ? (root.data.claude.remaining || 0) : 1.0 usedPercent: root.data && root.data.claude ? (root.data.claude.used_percent || 0) : 0 resetAt: root.data && root.data.claude ? (root.data.claude.reset_at || 0) : 0 clockTick: root.clockTick accentColor: root.accentColorFor(Math.round((root.data && root.data.claude ? root.data.claude.remaining : 1.0) * 100)) } // 3. GPT Card GroupCard { tag: "GPT" title: "GPT-OSS" models: root.data && root.data.gpt ? (root.data.gpt.models || "120B") : "120B" remaining: root.data && root.data.gpt ? (root.data.gpt.remaining || 0) : 1.0 usedPercent: root.data && root.data.gpt ? (root.data.gpt.used_percent || 0) : 0 resetAt: root.data && root.data.gpt ? (root.data.gpt.reset_at || 0) : 0 clockTick: root.clockTick accentColor: root.accentColorFor(Math.round((root.data && root.data.gpt ? root.data.gpt.remaining : 1.0) * 100)) } Item { Layout.fillHeight: true } // Footer RowLayout { Layout.fillWidth: true PC3.Label { text: root.lastUpdate ? i18n("SYNC %1", root.lastUpdate) : i18n("SYNC…") color: Kirigami.Theme.disabledTextColor font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 } Item { Layout.fillWidth: true } PC3.Label { text: "ROLLING" color: root.themeViolet font.bold: true font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 } } } } } }