From 75cf3e04b7edabbce2d820361b568b68a6aa057f Mon Sep 17 00:00:00 2001 From: enne2 Date: Sun, 6 Sep 2026 18:50:30 +0200 Subject: [PATCH] feat: dual-window 5h/7d monitoring, reset credits, and visual harmonization --- README.md | 38 +--- contents/config/main.xml | 8 +- contents/scripts/usage.sh | 49 ++-- contents/ui/configGeneral.qml | 2 +- contents/ui/main.qml | 410 +++++++++++++++++++++------------- metadata.json | 8 +- 6 files changed, 309 insertions(+), 206 deletions(-) diff --git a/README.md b/README.md index 20f8efe..c50f311 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,18 @@ [![Gitea](https://img.shields.io/badge/Gitea-enne2%2Fkde--codex--dashboard-blue?logo=gitea)](https://git.enne2.net/enne2/kde-codex-dashboard) -Widget desktop 224×224 per KDE Plasma 6 che mostra il **residuo settimanale** -di [OpenAI Codex](https://openai.com/codex), autenticato via OAuth -(profilo ChatGPT). +Widget desktop 224×224 per KDE Plasma 6 che mostra il **residuo a doppia finestra** (Settimanale 7D e Finestra 5 ore) di [OpenAI Codex](https://openai.com/codex), autenticato via OAuth (profilo ChatGPT). -Legge il token OAuth da `~/.pi/agent/auth.json` (provider `openai-codex` -configurato nel [pi agent](https://pi.dev)) e interroga l'endpoint -(non documentato) `GET https://chatgpt.com/backend-api/wham/usage`. +Legge il token OAuth da `~/.pi/agent/auth.json` (provider `openai-codex` configurato nel [pi agent](https://pi.dev)) e interroga l'endpoint `GET https://chatgpt.com/backend-api/wham/usage`. ## Funzionalità - **Piano**: Plus / Free / Team visibile nell'header. -- **Residuo settimanale** con barra di avanzamento e percentuale. -- **Countdown esatto** al reset (data/ora + giorni/ore/minuti mancanti), - direttamente dal campo `reset_at` della risposta API. -- **Crediti**: saldo crediti extra (se disponibili). -- **Stato limiti**: indicatore visivo se `limit_reached` o `spend_control.reached`. +- **Doppia Finestra Rate Limit**: + - **Finestra Settimanale (7D)** con barra di avanzamento e countdown esatto di reset. + - **Finestra 5 Ore (5H)** con barra di avanzamento e countdown esatto di reset. +- **Crediti di Reset**: badge con conteggio dei crediti di reset gratuiti disponibili (`rate_limit_reset_credits`). +- **Stato limiti**: indicatore visivo di blocco attivo (`limit_reached` o `spend_control.reached`). - **Aggiornamento automatico** configurabile + pulsante di refresh manuale. ## File @@ -25,28 +21,12 @@ configurato nel [pi agent](https://pi.dev)) e interroga l'endpoint ``` metadata.json metadata del pacchetto (Plasma/Applet) contents/config/main.xml schema della configurazione (kcfg) -contents/scripts/usage.sh backend: token OAuth, chiama wham/usage, stampa JSON -contents/ui/main.qml UI (dashboard 224×224) +contents/scripts/usage.sh backend: token OAuth, chiama wham/usage, stampa JSON +contents/ui/main.qml UI (dashboard 224×224) contents/ui/configGeneral.qml pagina di configurazione ``` -## Installazione - -```bash -# da sorgente -kpackagetool6 --type Plasma/Applet --install /home/enne2/Dev/org.enne2.codex.usage - -# da archivio -kpackagetool6 --type Plasma/Applet --install dist/org.enne2.codex.usage.tar.gz -``` - ## Dipendenze - `jq` e `curl` (per lo script di backend) - il token OAuth di OpenAI Codex in `~/.pi/agent/auth.json` sotto `openai-codex` (impostato da pi con `/login`) - -## Note - -- L'endpoint `/backend-api/wham/usage` è **non documentato** e potrebbe cambiare. -- Se il token OAuth scade, il plasmoide mostrerà "token scaduto". Riesegui `/login` - in pi (provider `openai-codex`) per rigenerarlo. \ No newline at end of file diff --git a/contents/config/main.xml b/contents/config/main.xml index 2f5964f..0025fd5 100644 --- a/contents/config/main.xml +++ b/contents/config/main.xml @@ -10,19 +10,19 @@ 30 3600 - Quanto spesso il plasmoide aggiorna i dati Codex. Minimo 30s. Default 300s (5 minuti). + Quanto spesso il plasmoide aggiorna le quote Codex. Minimo 30s. Default 300s (5 minuti). 60 0 100 - + 30 0 100 - + - \ No newline at end of file + diff --git a/contents/scripts/usage.sh b/contents/scripts/usage.sh index 8c23eb8..1973ce6 100755 --- a/contents/scripts/usage.sh +++ b/contents/scripts/usage.sh @@ -1,13 +1,15 @@ #!/usr/bin/env bash # Backend del plasmoide OpenAI Codex Usage. # Chiama l'endpoint (non documentato) GET https://chatgpt.com/backend-api/wham/usage -# con OAuth Bearer. La risposta contiene reset_at esatto → nessuna stima locale. +# con OAuth Bearer e chatgpt-account-id. set -u AUTH_JSON="${CODEX_AUTH_JSON:-$HOME/.pi/agent/auth.json}" TOKEN="" +ACCOUNT_ID="" if [ -f "$AUTH_JSON" ]; then TOKEN="$(jq -r '.["openai-codex"].access // empty' "$AUTH_JSON" 2>/dev/null)" + ACCOUNT_ID="$(jq -r '.["openai-codex"].accountId // empty' "$AUTH_JSON" 2>/dev/null)" fi [ -n "$TOKEN" ] || TOKEN="${CODEX_ACCESS_TOKEN:-}" [ -n "$TOKEN" ] || TOKEN="${OPENAI_CODEX_ACCESS_TOKEN:-}" @@ -17,29 +19,50 @@ if [ -z "$TOKEN" ]; then exit 0 fi -RESP="$(curl -sS --max-time 15 "https://chatgpt.com/backend-api/wham/usage" -H "Authorization: Bearer $TOKEN" 2>/dev/null)" +REQ_HEADERS=(-H "Authorization: Bearer $TOKEN") +if [ -n "$ACCOUNT_ID" ]; then + REQ_HEADERS+=(-H "chatgpt-account-id: $ACCOUNT_ID") +fi +REQ_HEADERS+=(-H "User-Agent: pi-agent/1.0") + +RESP="$(curl -sS --max-time 15 "${REQ_HEADERS[@]}" "https://chatgpt.com/backend-api/wham/usage" 2>/dev/null)" CURL_RC=$? if [ "$CURL_RC" -ne 0 ] || [ -z "$RESP" ]; then printf '{"ok":false,"error":"fetch_failed","detail":"curl exit %s"}\n' "$CURL_RC" exit 0 fi -# Se il token è scaduto, l'API restituisce HTML di login invece di JSON. +# Se il token è scaduto o non valido, l'API non restituisce rate_limit. if ! printf '%s' "$RESP" | jq -e '.rate_limit.primary_window.used_percent | type == "number"' >/dev/null 2>&1; then - printf '{"ok":false,"error":"token_expired","detail":"token Codex scaduto, riesegui /login in pi"}\n' + printf '{"ok":false,"error":"token_expired","detail":"token Codex scaduto o non valido, riesegui /login in pi"}\n' exit 0 fi printf '%s' "$RESP" | jq -c '{ ok: true, - plan_type: .plan_type, - remaining: ((100 - .rate_limit.primary_window.used_percent) / 100), - used_percent: .rate_limit.primary_window.used_percent, - reset_at: .rate_limit.primary_window.reset_at, - limit_window: .rate_limit.primary_window.limit_window_seconds, - credits: { balance: .credits.balance, has_credits: .credits.has_credits }, - spend_reached: .spend_control.reached, - limit_reached: .rate_limit.limit_reached + plan_type: (.plan_type // "—"), + allowed: (.rate_limit.allowed // true), + limit_reached: (.rate_limit.limit_reached // false), + primary_window: { + used_percent: (.rate_limit.primary_window.used_percent // 0), + remaining: ((100 - (.rate_limit.primary_window.used_percent // 0)) / 100), + reset_at: .rate_limit.primary_window.reset_at, + limit_window_seconds: (.rate_limit.primary_window.limit_window_seconds // 18000), + reset_after_seconds: .rate_limit.primary_window.reset_after_seconds + }, + secondary_window: { + used_percent: (.rate_limit.secondary_window.used_percent // 0), + remaining: ((100 - (.rate_limit.secondary_window.used_percent // 0)) / 100), + reset_at: .rate_limit.secondary_window.reset_at, + limit_window_seconds: (.rate_limit.secondary_window.limit_window_seconds // 604800), + reset_after_seconds: .rate_limit.secondary_window.reset_after_seconds + }, + reset_credits: (.rate_limit_reset_credits.available_count // 0), + credits: { + balance: (.credits.balance // "0"), + has_credits: (.credits.has_credits // false) + }, + spend_reached: (.spend_control.reached // false) }' 2>/dev/null || printf '{"ok":false,"error":"bad_json","detail":"risposta non valida da wham/usage"}\n' -exit 0 \ No newline at end of file +exit 0 diff --git a/contents/ui/configGeneral.qml b/contents/ui/configGeneral.qml index c84319b..d3b9175 100644 --- a/contents/ui/configGeneral.qml +++ b/contents/ui/configGeneral.qml @@ -54,4 +54,4 @@ Item { Item { Layout.fillHeight: true } } -} \ No newline at end of file +} diff --git a/contents/ui/main.qml b/contents/ui/main.qml index 81ef6b8..1c154a0 100644 --- a/contents/ui/main.qml +++ b/contents/ui/main.qml @@ -11,7 +11,7 @@ import org.kde.kirigami as Kirigami PlasmoidItem { id: root - property var data: null // { ok, plan_type, remaining, used_percent, reset_at, limit_window, credits, spend_reached, limit_reached } + property var data: null property string lastUpdate: "" property string errorMsg: "" property bool loading: false @@ -21,13 +21,17 @@ PlasmoidItem { 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("Codex — utilizzo settimanale") - toolTipSubText: root.data ? i18n("Residuo: %1% · %2", - Math.round((root.data.remaining || 0) * 100), - root.data.plan_type || "—") : (root.errorMsg || "") + 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 @@ -50,7 +54,7 @@ PlasmoidItem { if (parsed && parsed.ok === false) { root.errorMsg = parsed.error + (parsed.detail ? " — " + parsed.detail : "") root.data = null - } else if (parsed && parsed.ok && parsed.remaining !== undefined) { + } 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") @@ -73,25 +77,41 @@ PlasmoidItem { } function shellQuote(s) { return "'" + String(s).replace(/'/g, "'\"'\"'") + "'" } - function remainingPct() { return root.data ? Math.round((root.data.remaining || 0) * 100) : 0 } - function usedPct() { return root.data ? Math.round(root.data.used_percent || 0) : 0 } - function accentColor() { - const pct = remainingPct() - if (pct <= root.critThreshold) return Kirigami.Theme.negativeTextColor - if (pct <= root.warnThreshold) return Kirigami.Theme.neutralTextColor - return "#7cb342" + 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 resetMoment() { - const tick = root.clockTick - if (!root.data || !root.data.reset_at) return "—" - const d = new Date(root.data.reset_at * 1000) - return d.toLocaleDateString(Qt.locale(), "dd/MM") + " " + d.toLocaleTimeString(Qt.locale(), "HH:mm") + function secRemainingPct() { + return root.data && root.data.secondary_window ? Math.round((root.data.secondary_window.remaining || 0) * 100) : 0 } - function resetRemaining() { + 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 (!root.data || !root.data.reset_at) return "—" - let mins = Math.max(0, Math.floor((root.data.reset_at * 1000 - Date.now()) / 60000)) + 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 @@ -100,14 +120,6 @@ PlasmoidItem { return days > 0 ? i18n("%1g %2h", days, hours) : i18n("%1h %2m", hours, mins) } - function windowLabel() { - if (!root.data || !root.data.limit_window) return "7D" - const secs = root.data.limit_window - if (secs >= 604800) return "7D" - if (secs >= 3600) return Math.round(secs / 3600) + "H" - return Math.round(secs / 60) + "M" - } - Component.onCompleted: root.refresh() Timer { interval: Math.max(30, root.refreshSecs) * 1000 @@ -129,7 +141,7 @@ PlasmoidItem { Kirigami.Icon { anchors.fill: parent source: Plasmoid.icon - color: root.data ? root.accentColor() : Kirigami.Theme.disabledTextColor + color: root.data ? root.globalAccentColor() : Kirigami.Theme.disabledTextColor } MouseArea { anchors.fill: parent; onClicked: Plasmoid.expanded = !Plasmoid.expanded } } @@ -144,42 +156,47 @@ PlasmoidItem { ColumnLayout { anchors.fill: parent - anchors.margins: 9 - spacing: 5 + anchors.margins: 7 + spacing: 4 - // header + // Header RowLayout { Layout.fillWidth: true + spacing: 5 Kirigami.Icon { source: Plasmoid.icon - color: "#7cb342" - Layout.preferredWidth: 20 - Layout.preferredHeight: 20 + 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 + 1 + font.pointSize: Kirigami.Theme.defaultFont.pointSize } PC3.Label { - text: root.data ? (root.data.plan_type || "—").toUpperCase() : "—" - color: "#7cb342" + text: root.data ? (root.data.plan_type || "—").toUpperCase() : i18n("QUOTA DASHBOARD") + color: root.brandColor font.bold: true - font.pointSize: Kirigami.Theme.defaultFont.pointSize - 2 + font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 } } Item { Layout.fillWidth: true } + Rectangle { - Layout.preferredWidth: 9 - Layout.preferredHeight: 9 - radius: 5 - color: root.errorMsg ? Kirigami.Theme.negativeTextColor : "#7cb342" + 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() } @@ -188,7 +205,7 @@ PlasmoidItem { Rectangle { Layout.fillWidth: true Layout.preferredHeight: 1 - color: Qt.rgba(0.49, 0.76, 0.26, 0.45) + color: Qt.rgba(0.49, 0.76, 0.26, 0.35) } PC3.Label { @@ -196,95 +213,18 @@ PlasmoidItem { Layout.fillWidth: true wrapMode: Text.Wrap color: Kirigami.Theme.negativeTextColor + font.pointSize: Kirigami.Theme.defaultFont.pointSize - 2 text: i18n("Errore: %1", root.errorMsg) } - // barra principale + // Card 1: Settimanale (7D) Rectangle { Layout.fillWidth: true - Layout.preferredHeight: 72 - radius: 6 - color: Qt.rgba(0, 0, 0, 0.26) - border.width: 1 - border.color: root.accentColor() - - ColumnLayout { - anchors.fill: parent - anchors.margins: 7 - spacing: 3 - - RowLayout { - Layout.fillWidth: true - spacing: 5 - Rectangle { - Layout.preferredWidth: 24 - Layout.preferredHeight: 15 - radius: 8 - color: root.accentColor() - PC3.Label { - anchors.centerIn: parent - text: root.windowLabel() - color: "#101012" - font.bold: true - font.pointSize: Kirigami.Theme.defaultFont.pointSize - 2 - } - } - PC3.Label { - text: i18n("Settimanale") - font.bold: true - Layout.fillWidth: true - } - PC3.Label { - text: root.remainingPct() + "%" - color: root.accentColor() - font.bold: true - font.pointSize: Kirigami.Theme.defaultFont.pointSize + 3 - } - } - - Item { - Layout.fillWidth: true - Layout.preferredHeight: 8 - Rectangle { - anchors.fill: parent - radius: 4 - 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.remaining : 0)) - height: parent.height - radius: 4 - color: root.accentColor() - } - } - - RowLayout { - Layout.fillWidth: true - PC3.Label { - text: i18n("%1% usato", root.usedPct()) - color: Kirigami.Theme.disabledTextColor - font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 - } - Item { Layout.fillWidth: true } - PC3.Label { - visible: Boolean(root.data && root.data.limit_reached) - text: i18n("✕ LIMITE") - color: Kirigami.Theme.negativeTextColor - font.bold: true - font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 - } - } - } - } - - // reset + crediti - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 54 + Layout.preferredHeight: 52 radius: 5 - color: Qt.rgba(0, 0, 0, 0.28) + color: Qt.rgba(0, 0, 0, 0.25) + border.width: 1 + border.color: root.accentColorFor(root.secRemainingPct()) ColumnLayout { anchors.fill: parent @@ -293,40 +233,63 @@ PlasmoidItem { RowLayout { Layout.fillWidth: true - PC3.Label { - text: i18n("RESET") - color: Kirigami.Theme.disabledTextColor - font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 + 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 + } } - Item { Layout.fillWidth: true } PC3.Label { - text: root.resetMoment() - color: root.accentColor() - font.bold: true - font.pointSize: Kirigami.Theme.defaultFont.pointSize - 2 - } - } - RowLayout { - Layout.fillWidth: true - PC3.Label { - text: root.resetRemaining() - color: "#7cb342" + text: i18n("Settimanale") font.bold: true font.pointSize: Kirigami.Theme.defaultFont.pointSize - 1 + Layout.fillWidth: true } - Item { Layout.fillWidth: true } PC3.Label { - text: root.data && root.data.credits ? i18n("Cred. %1", root.data.credits.balance || "0") : "" - color: Kirigami.Theme.disabledTextColor - font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3 + 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 { - visible: Boolean(root.data && root.data.spend_reached) - text: i18n("Spend control raggiunto") - color: Kirigami.Theme.negativeTextColor + 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 } @@ -334,6 +297,139 @@ PlasmoidItem { } } + // 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 { @@ -343,13 +439,17 @@ PlasmoidItem { } Item { Layout.fillWidth: true } PC3.Label { - text: root.data && root.data.plan_type ? root.data.plan_type.toUpperCase() : "" - color: Kirigami.Theme.disabledTextColor - font.bold: true + 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 } } } } } -} \ No newline at end of file +} diff --git a/metadata.json b/metadata.json index 34db869..3f2130c 100644 --- a/metadata.json +++ b/metadata.json @@ -4,13 +4,13 @@ "Id": "org.enne2.codex.usage", "Name": "OpenAI Codex Usage", "Name[it]": "Utilizzo OpenAI Codex", - "Description": "Shows the weekly usage allowance for OpenAI Codex (ChatGPT OAuth profile), with exact reset countdown, plan type, and credits", - "Description[it]": "Mostra il residuo settimanale di OpenAI Codex (profilo ChatGPT OAuth), con countdown esatto del reset, tipo piano e crediti", + "Description": "Shows dual-window (5h and 7d) usage allowance for OpenAI Codex (ChatGPT OAuth profile), with exact reset countdown, plan type, and banked reset credits", + "Description[it]": "Mostra il residuo a doppia finestra (5h e 7d) di OpenAI Codex (profilo ChatGPT OAuth), con countdown esatto del reset, tipo piano e crediti di reset", "Icon": "code-context", "Category": "System Information", "License": "GPL-3.0", - "Version": "1.0.0", + "Version": "1.1.0", "Authors": [ { "Name": "enne2" } ] }, "X-Plasma-API-Minimum-Version": "6.0" -} \ No newline at end of file +}