feat: dashboard KDE Plasma 6 per utilizzo OpenAI Codex
Widget desktop 224x224 che mostra: - residuo settimanale con barra e percentuale - countdown esatto al reset (reset_at dall'API) - piano (Plus/Free), crediti, stato limiti - backend bash che chiama https://chatgpt.com/backend-api/wham/usage - legge il token OAuth da ~/.pi/agent/auth.json (provider openai-codex)
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
node_modules/
|
||||||
|
/tmp/
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# OpenAI Codex Usage — plasmoide KDE Plasma 6
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
- **Aggiornamento automatico** configurabile + pulsante di refresh manuale.
|
||||||
|
|
||||||
|
## File
|
||||||
|
|
||||||
|
```
|
||||||
|
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/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.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
|
||||||
|
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
|
||||||
|
<kcfgfile name=""/>
|
||||||
|
<group name="General">
|
||||||
|
<entry name="refreshIntervalSec" type="Int">
|
||||||
|
<default>300</default>
|
||||||
|
<min>30</min>
|
||||||
|
<max>3600</max>
|
||||||
|
<label>Intervallo di aggiornamento (secondi)</label>
|
||||||
|
<whatsthis>Quanto spesso il plasmoide aggiorna i dati Codex. Minimo 30s. Default 300s (5 minuti).</whatsthis>
|
||||||
|
</entry>
|
||||||
|
<entry name="warnThreshold" type="Int">
|
||||||
|
<default>60</default>
|
||||||
|
<min>0</min>
|
||||||
|
<max>100</max>
|
||||||
|
<label>Soglia gialla per il residuo (%)</label>
|
||||||
|
</entry>
|
||||||
|
<entry name="critThreshold" type="Int">
|
||||||
|
<default>30</default>
|
||||||
|
<min>0</min>
|
||||||
|
<max>100</max>
|
||||||
|
<label>Soglia rossa per il residuo (%)</label>
|
||||||
|
</entry>
|
||||||
|
</group>
|
||||||
|
</kcfg>
|
||||||
Executable
+45
@@ -0,0 +1,45 @@
|
|||||||
|
#!/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.
|
||||||
|
set -u
|
||||||
|
|
||||||
|
AUTH_JSON="${CODEX_AUTH_JSON:-$HOME/.pi/agent/auth.json}"
|
||||||
|
TOKEN=""
|
||||||
|
if [ -f "$AUTH_JSON" ]; then
|
||||||
|
TOKEN="$(jq -r '.["openai-codex"].access // empty' "$AUTH_JSON" 2>/dev/null)"
|
||||||
|
fi
|
||||||
|
[ -n "$TOKEN" ] || TOKEN="${CODEX_ACCESS_TOKEN:-}"
|
||||||
|
[ -n "$TOKEN" ] || TOKEN="${OPENAI_CODEX_ACCESS_TOKEN:-}"
|
||||||
|
|
||||||
|
if [ -z "$TOKEN" ]; then
|
||||||
|
printf '{"ok":false,"error":"no_token","detail":"nessun token Codex in %s"}\n' "$AUTH_JSON"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
RESP="$(curl -sS --max-time 15 "https://chatgpt.com/backend-api/wham/usage" -H "Authorization: Bearer $TOKEN" 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.
|
||||||
|
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'
|
||||||
|
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
|
||||||
|
}' 2>/dev/null || printf '{"ok":false,"error":"bad_json","detail":"risposta non valida da wham/usage"}\n'
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* configGeneral.qml — pagina di configurazione del plasmoide OpenAI Codex Usage.
|
||||||
|
*/
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import org.kde.plasma.components 3.0 as PC3
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: page
|
||||||
|
|
||||||
|
property alias cfg_refreshIntervalSec: refreshSpin.value
|
||||||
|
property alias cfg_warnThreshold: warnSpin.value
|
||||||
|
property alias cfg_critThreshold: critSpin.value
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Kirigami.Units.largeSpacing
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
PC3.Label {
|
||||||
|
text: i18n("Intervallo di aggiornamento (secondi)")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
PC3.SpinBox {
|
||||||
|
id: refreshSpin
|
||||||
|
from: 30
|
||||||
|
to: 3600
|
||||||
|
stepSize: 30
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
PC3.Label {
|
||||||
|
text: i18n("Soglia residuo giallo (%)")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
PC3.SpinBox {
|
||||||
|
id: warnSpin
|
||||||
|
from: 0
|
||||||
|
to: 100
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
PC3.Label {
|
||||||
|
text: i18n("Soglia residuo rosso (%)")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
PC3.SpinBox {
|
||||||
|
id: critSpin
|
||||||
|
from: 0
|
||||||
|
to: 100
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillHeight: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,355 @@
|
|||||||
|
/* 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 // { ok, plan_type, remaining, used_percent, reset_at, limit_window, credits, spend_reached, limit_reached }
|
||||||
|
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
|
||||||
|
|
||||||
|
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 || "")
|
||||||
|
|
||||||
|
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.remaining !== undefined) {
|
||||||
|
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 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 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 resetRemaining() {
|
||||||
|
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 (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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
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.accentColor() : 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: 9
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
// header
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Kirigami.Icon {
|
||||||
|
source: Plasmoid.icon
|
||||||
|
color: "#7cb342"
|
||||||
|
Layout.preferredWidth: 20
|
||||||
|
Layout.preferredHeight: 20
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
PC3.Label {
|
||||||
|
text: i18n("CODEX")
|
||||||
|
font.bold: true
|
||||||
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize + 1
|
||||||
|
}
|
||||||
|
PC3.Label {
|
||||||
|
text: root.data ? (root.data.plan_type || "—").toUpperCase() : "—"
|
||||||
|
color: "#7cb342"
|
||||||
|
font.bold: true
|
||||||
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
Rectangle {
|
||||||
|
Layout.preferredWidth: 9
|
||||||
|
Layout.preferredHeight: 9
|
||||||
|
radius: 5
|
||||||
|
color: root.errorMsg ? Kirigami.Theme.negativeTextColor : "#7cb342"
|
||||||
|
}
|
||||||
|
PC3.Button {
|
||||||
|
icon.name: "view-refresh"
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
enabled: !root.loading
|
||||||
|
onClicked: root.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
color: Qt.rgba(0.49, 0.76, 0.26, 0.45)
|
||||||
|
}
|
||||||
|
|
||||||
|
PC3.Label {
|
||||||
|
visible: root.errorMsg.length > 0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
color: Kirigami.Theme.negativeTextColor
|
||||||
|
text: i18n("Errore: %1", root.errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// barra principale
|
||||||
|
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
|
||||||
|
radius: 5
|
||||||
|
color: Qt.rgba(0, 0, 0, 0.28)
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 5
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
PC3.Label {
|
||||||
|
text: i18n("RESET")
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
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"
|
||||||
|
font.bold: true
|
||||||
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 1
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
PC3.Label {
|
||||||
|
visible: Boolean(root.data && root.data.spend_reached)
|
||||||
|
text: i18n("Spend control raggiunto")
|
||||||
|
color: Kirigami.Theme.negativeTextColor
|
||||||
|
font.bold: true
|
||||||
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.plan_type ? root.data.plan_type.toUpperCase() : ""
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
font.bold: true
|
||||||
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"KPackageStructure": "Plasma/Applet",
|
||||||
|
"KPlugin": {
|
||||||
|
"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",
|
||||||
|
"Icon": "code-context",
|
||||||
|
"Category": "System Information",
|
||||||
|
"License": "GPL-3.0",
|
||||||
|
"Version": "1.0.0",
|
||||||
|
"Authors": [ { "Name": "enne2" } ]
|
||||||
|
},
|
||||||
|
"X-Plasma-API-Minimum-Version": "6.0"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user