/* OpenAI Codex 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 brandColor: "#7cb342" Plasmoid.title: i18n("OpenAI Codex") Plasmoid.icon: "code-context" Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground toolTipMainText: i18n("OpenAI Codex — Rate Limits") toolTipSubText: root.data ? i18n("Settimana (7D): %1% · Finestra 5h: %2% · Piano: %3 · Reset disp: %4", root.secRemainingPct(), root.primRemainingPct(), (root.data.plan_type || "—").toUpperCase(), root.data.reset_credits || 0) : (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 parsed = JSON.parse((result["stdout"] || "").trim()) if (parsed && parsed.ok === false) { root.errorMsg = parsed.error + (parsed.detail ? " — " + parsed.detail : "") root.data = null } else if (parsed && parsed.ok && parsed.primary_window && parsed.secondary_window) { 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 primRemainingPct() { return root.data && root.data.primary_window ? Math.round((root.data.primary_window.remaining || 0) * 100) : 0 } function primUsedPct() { return root.data && root.data.primary_window ? Math.round(root.data.primary_window.used_percent || 0) : 0 } function secRemainingPct() { return root.data && root.data.secondary_window ? Math.round((root.data.secondary_window.remaining || 0) * 100) : 0 } function secUsedPct() { return root.data && root.data.secondary_window ? Math.round(root.data.secondary_window.used_percent || 0) : 0 } function effectiveRemainingPct() { if (!root.data) return 0 return Math.min(primRemainingPct(), secRemainingPct()) } function accentColorFor(pct) { if (pct <= root.critThreshold) return Kirigami.Theme.negativeTextColor if (pct <= root.warnThreshold) return Kirigami.Theme.neutralTextColor return root.brandColor } function globalAccentColor() { if (!root.data) return Kirigami.Theme.disabledTextColor if (root.data.limit_reached || root.data.allowed === false) return Kirigami.Theme.negativeTextColor return accentColorFor(effectiveRemainingPct()) } function formatResetRemaining(resetAt) { const tick = root.clockTick if (!resetAt) return "—" let mins = Math.max(0, Math.floor((resetAt * 1000 - Date.now()) / 60000)) if (mins === 0) return i18n("ora") const days = Math.floor(mins / 1440) mins -= days * 1440 const hours = Math.floor(mins / 60) mins -= hours * 60 return days > 0 ? i18n("%1g %2h", days, hours) : i18n("%1h %2m", hours, mins) } 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.globalAccentColor() : 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: 7 spacing: 4 // Header RowLayout { Layout.fillWidth: true spacing: 5 Kirigami.Icon { source: Plasmoid.icon color: root.brandColor Layout.preferredWidth: 18 Layout.preferredHeight: 18 } ColumnLayout { spacing: 0 PC3.Label { text: i18n("CODEX") font.bold: true font.pointSize: Kirigami.Theme.defaultFont.pointSize } PC3.Label { text: root.data ? (root.data.plan_type || "—").toUpperCase() : i18n("QUOTA DASHBOARD") color: root.brandColor 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.data && (root.data.limit_reached || root.data.allowed === false) ? Kirigami.Theme.negativeTextColor : root.brandColor) } PC3.Button { icon.name: "view-refresh" display: QQC2.AbstractButton.IconOnly Layout.preferredWidth: 22 Layout.preferredHeight: 22 enabled: !root.loading onClicked: root.refresh() } } Rectangle { Layout.fillWidth: true Layout.preferredHeight: 1 color: Qt.rgba(0.49, 0.76, 0.26, 0.35) } PC3.Label { visible: root.errorMsg.length > 0 Layout.fillWidth: true wrapMode: Text.Wrap color: Kirigami.Theme.negativeTextColor font.pointSize: Kirigami.Theme.defaultFont.pointSize - 2 text: i18n("Errore: %1", root.errorMsg) } // Card 1: Settimanale (7D) Rectangle { Layout.fillWidth: true Layout.preferredHeight: 52 radius: 5 color: Qt.rgba(0, 0, 0, 0.25) border.width: 1 border.color: root.accentColorFor(root.secRemainingPct()) ColumnLayout { anchors.fill: parent anchors.margins: 5 spacing: 2 RowLayout { Layout.fillWidth: true spacing: 4 Rectangle { Layout.preferredWidth: 22 Layout.preferredHeight: 14 radius: 7 color: root.accentColorFor(root.secRemainingPct()) PC3.Label { anchors.centerIn: parent text: "7D" color: "#101012" font.bold: true font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 } } PC3.Label { text: i18n("Settimanale") font.bold: true font.pointSize: Kirigami.Theme.defaultFont.pointSize - 1 Layout.fillWidth: true } PC3.Label { text: root.secRemainingPct() + "%" color: root.accentColorFor(root.secRemainingPct()) font.bold: true font.pointSize: Kirigami.Theme.defaultFont.pointSize + 1 } } Item { Layout.fillWidth: true Layout.preferredHeight: 6 Rectangle { anchors.fill: parent radius: 3 color: Qt.rgba(0, 0, 0, 0.55) } Rectangle { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter width: parent.width * Math.max(0, Math.min(1, root.data && root.data.secondary_window ? root.data.secondary_window.remaining : 0)) height: parent.height radius: 3 color: root.accentColorFor(root.secRemainingPct()) } } RowLayout { Layout.fillWidth: true PC3.Label { text: i18n("%1% usato", root.secUsedPct()) color: Kirigami.Theme.disabledTextColor font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 } Item { Layout.fillWidth: true } PC3.Label { text: i18n("Reset: %1", root.formatResetRemaining(root.data && root.data.secondary_window ? root.data.secondary_window.reset_at : null)) color: root.accentColorFor(root.secRemainingPct()) font.bold: true font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 } } } } // Card 2: Finestra 5 Ore (5H) Rectangle { Layout.fillWidth: true Layout.preferredHeight: 52 radius: 5 color: Qt.rgba(0, 0, 0, 0.25) border.width: 1 border.color: root.accentColorFor(root.primRemainingPct()) ColumnLayout { anchors.fill: parent anchors.margins: 5 spacing: 2 RowLayout { Layout.fillWidth: true spacing: 4 Rectangle { Layout.preferredWidth: 22 Layout.preferredHeight: 14 radius: 7 color: root.accentColorFor(root.primRemainingPct()) PC3.Label { anchors.centerIn: parent text: "5H" color: "#101012" font.bold: true font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 } } PC3.Label { text: i18n("Finestra 5 ore") font.bold: true font.pointSize: Kirigami.Theme.defaultFont.pointSize - 1 Layout.fillWidth: true } PC3.Label { text: root.primRemainingPct() + "%" color: root.accentColorFor(root.primRemainingPct()) font.bold: true font.pointSize: Kirigami.Theme.defaultFont.pointSize + 1 } } Item { Layout.fillWidth: true Layout.preferredHeight: 6 Rectangle { anchors.fill: parent radius: 3 color: Qt.rgba(0, 0, 0, 0.55) } Rectangle { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter width: parent.width * Math.max(0, Math.min(1, root.data && root.data.primary_window ? root.data.primary_window.remaining : 0)) height: parent.height radius: 3 color: root.accentColorFor(root.primRemainingPct()) } } RowLayout { Layout.fillWidth: true PC3.Label { text: i18n("%1% usato", root.primUsedPct()) color: Kirigami.Theme.disabledTextColor font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 } Item { Layout.fillWidth: true } PC3.Label { text: i18n("Reset: %1", root.formatResetRemaining(root.data && root.data.primary_window ? root.data.primary_window.reset_at : null)) color: root.accentColorFor(root.primRemainingPct()) font.bold: true font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 } } } } // Card 3: Crediti Extra + Reset Disponibili Rectangle { Layout.fillWidth: true Layout.preferredHeight: 40 radius: 5 color: Qt.rgba(0, 0, 0, 0.25) RowLayout { anchors.fill: parent anchors.leftMargin: 7 anchors.rightMargin: 7 anchors.topMargin: 4 anchors.bottomMargin: 6 spacing: 8 ColumnLayout { spacing: 1 PC3.Label { text: i18n("CREDITI EXTRA") color: Kirigami.Theme.disabledTextColor font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 } PC3.Label { text: root.data && root.data.credits && root.data.credits.balance ? "$" + root.data.credits.balance : "$0.00" font.bold: true color: root.brandColor font.pointSize: Kirigami.Theme.defaultFont.pointSize - 1 } } Item { Layout.fillWidth: true } ColumnLayout { spacing: 1 Layout.alignment: Qt.AlignRight PC3.Label { text: i18n("RESET SCORTA") color: Kirigami.Theme.disabledTextColor Layout.alignment: Qt.AlignRight font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 } PC3.Label { text: (root.data && root.data.reset_credits !== undefined) ? i18n("↺ %1 disp.", root.data.reset_credits) : "—" font.bold: true color: "#38bdf8" Layout.alignment: Qt.AlignRight font.pointSize: Kirigami.Theme.defaultFont.pointSize - 1 } } } } // 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: root.data && (root.data.limit_reached || root.data.allowed === false) ? i18n("✕ LIMITE ATTIVO") : i18n("STATO ATTIVO") color: root.data && (root.data.limit_reached || root.data.allowed === false) ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.disabledTextColor font.bold: Boolean(root.data && (root.data.limit_reached || root.data.allowed === false)) font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 } } } } } }