Strata dashboard plasmoid v1.2: barre a soglie e velocita decode/prefill
- card GPU con barre progressive: VRAM (informativa), utilizzo 60/85 %, temperatura 75/85 C, potenza su budget 350 W (ambra 70 %, rosso 90 %) - velocita del motore: DEC (generazione) e PRE (prefill) in tok/s, live durante la generazione, ultima richiesta da fermo (badge LIVE/ULTIMA); semantica inversa: verde sopra la nominale, ambra fra 50 % e nominale - card servizio + controllo unificata con switch load/unload nativo e stato che si chiude sulla fase osservata - MetricBar condiviso con levelColor()/speedColor() e soglie in testa al QML - README: requisiti, campi del control plane, tabella soglie, limiti del box - avatar pixel-art 512x512 in assets/repository-avatar.png
This commit is contained in:
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
# action.sh <load|unload> [uniq] — POST in background, esce subito.
|
||||
API="${STRATACTL_API:-http://172.30.150.224:18090}"
|
||||
KEYF="$HOME/.config/strata-control/control.key"
|
||||
[ $# -ge 1 ] || exit 1
|
||||
[ -r "$KEYF" ] || exit 1
|
||||
setsid curl -s -m 320 -X POST -H "X-API-Key: $(cat "$KEYF")" "$API/$1" >/dev/null 2>&1 < /dev/null &
|
||||
exit 0
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# Stato del control plane Strata (JSON su stdout).
|
||||
API="${STRATACTL_API:-http://172.30.150.224:18090}"
|
||||
KEYF="$HOME/.config/strata-control/control.key"
|
||||
if [ ! -r "$KEYF" ]; then printf '{"error":"chiave mancante"}'; exit 0; fi
|
||||
out=$(curl -s -m 6 -H "X-API-Key: $(cat "$KEYF")" "$API/status" 2>/dev/null)
|
||||
if [ -z "$out" ]; then printf '{"error":"control plane non raggiungibile"}'; else printf '%s' "$out"; fi
|
||||
@@ -0,0 +1,581 @@
|
||||
/* Strata (qwen3.8-flash-next) — stato e controllo del servizio di inferenza su seonse-engine-02.
|
||||
*
|
||||
* Adattato da llama-gsq3: ora parla con il control plane di Strata, che espone
|
||||
* load/unload nativi (/load, /unload?wait=0). Strata serve UNA richiesta alla volta:
|
||||
* la concorrenza e' solo informativa, non c'e' piu' il selettore di slot.
|
||||
* Stile invariato rispetto agli altri plasmoidi enne2.
|
||||
*/
|
||||
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
|
||||
|
||||
Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground
|
||||
preferredRepresentation: fullRepresentation
|
||||
switchWidth: 150
|
||||
switchHeight: 150
|
||||
|
||||
readonly property color themeGreen: "#4ade80"
|
||||
readonly property color themeIndigo: "#818cf8"
|
||||
readonly property color themeViolet: "#a855f7"
|
||||
|
||||
// ---------- palette dei livelli ----------
|
||||
readonly property color okColor: "#4ade80" // sotto la soglia di attenzione
|
||||
readonly property color warnColor: "#fbbf24" // attenzione
|
||||
readonly property color critColor: "#f87171" // critico
|
||||
readonly property color naColor: "#5b6b7d" // valore non disponibile
|
||||
|
||||
// ---------- soglie GPU (ritocca qui) ----------
|
||||
// Le barre PWR si riempiono sul budget: ambra oltre il 70 %, rosso oltre il 90 %.
|
||||
// NVIDIA L40S: TDP 350 W, 48 GB; la VRAM resta informativa (colore accento).
|
||||
readonly property real gpuPowerMax: 350
|
||||
readonly property real utilWarn: 60
|
||||
readonly property real utilCrit: 85
|
||||
readonly property real tempWarn: 75
|
||||
readonly property real tempCrit: 85
|
||||
readonly property real powerWarnRatio: 0.70
|
||||
readonly property real powerCritRatio: 0.90
|
||||
|
||||
// ---------- velocità di generazione e prefill (ritocca qui) ----------
|
||||
// Nominali misurate sul L40S con MTP (2026-10-23): decode ~40 tok/s (picchi ~69),
|
||||
// prefill ~2800-3800 tok/s. Fondo scala = massimo ragionevole.
|
||||
readonly property real decodeNominal: 40
|
||||
readonly property real decodeMax: 120
|
||||
readonly property real prefillNominal: 3000
|
||||
readonly property real prefillMax: 5000
|
||||
|
||||
readonly property string scripts: Qt.resolvedUrl("../scripts/").toString().replace(/^file:\/\//, "")
|
||||
readonly property string pollSource: "/bin/bash " + root.scripts + "status.sh"
|
||||
|
||||
property var st: ({})
|
||||
property string phase: "unknown"
|
||||
property string errorMsg: ""
|
||||
property string lastUpdate: ""
|
||||
property bool pending: false
|
||||
property string pendingTarget: ""
|
||||
property int pendingTries: 0
|
||||
property int counter: 0
|
||||
property string actSource: ""
|
||||
|
||||
readonly property var modelInfo: root.st.model || ({})
|
||||
readonly property var gpuInfo: root.st.gpu || ({})
|
||||
readonly property var liveInfo: root.modelInfo.live || ({})
|
||||
readonly property var lastTimings: root.modelInfo.last_timings || ({})
|
||||
readonly property bool speedsLive: typeof root.liveInfo.tok_s === "number"
|
||||
// durante la generazione valgono i valori live, da idle quelli dell'ultima richiesta
|
||||
readonly property real decodeTokS: (typeof root.liveInfo.tok_s === "number")
|
||||
? root.liveInfo.tok_s : root.metric(root.lastTimings.predicted_per_second)
|
||||
readonly property real prefillTokS: (typeof root.liveInfo.prefill_tok_s_mean === "number")
|
||||
? root.liveInfo.prefill_tok_s_mean : root.metric(root.lastTimings.prompt_per_second)
|
||||
readonly property var concInfo: root.modelInfo.concurrency || ({})
|
||||
readonly property bool loaded: root.modelInfo.loaded === true
|
||||
readonly property bool running: root.pending ? (root.pendingTarget === "load")
|
||||
: (root.phase === "loaded" || root.phase === "generating" || root.phase === "loading")
|
||||
|
||||
Plasmoid.busy: root.pending
|
||||
toolTipMainText: "Strata — " + root.phaseWord()
|
||||
toolTipSubText: {
|
||||
const m = root.modelInfo, g = root.gpuInfo
|
||||
if (root.errorMsg.length > 0) return root.errorMsg
|
||||
return "fase: " + root.phase
|
||||
+ "\nmodello: " + (m.alias || "—") + (m.kv ? " (" + m.kv + ")" : "")
|
||||
+ "\ncontesto: " + root.ctxLabel() + " per richiesta"
|
||||
+ "\nconcorrenza: " + (root.concInfo.serving || 1) + " · vision: " + (m.vision ? "attiva" : "non disponibile")
|
||||
+ "\nVRAM: " + (g.mem_used_mib / 1024).toFixed(1) + " / " + (g.mem_total_mib / 1024).toFixed(1) + " GiB"
|
||||
+ "\nutil " + (g.util_pct || 0).toFixed(0) + "% · " + (g.temp_c || 0).toFixed(0) + " °C · " + (g.power_w || 0).toFixed(0) + " W"
|
||||
+ "\ndecode " + root.fmt(root.decodeTokS, 1, " tok/s") + " · prefill " + root.fmt(root.prefillTokS, 0, " tok/s")
|
||||
+ (root.speedsLive ? " (live)" : " (ultima richiesta)")
|
||||
}
|
||||
|
||||
function phaseAccent() {
|
||||
switch (root.phase) {
|
||||
case "loaded": return root.themeGreen
|
||||
case "generating": return root.themeIndigo
|
||||
case "loading": return root.themeViolet
|
||||
case "unreachable": return Kirigami.Theme.negativeTextColor
|
||||
default: return Kirigami.Theme.disabledTextColor
|
||||
}
|
||||
}
|
||||
function phaseWord() {
|
||||
if (root.pending) return root.pendingTarget === "load" ? "CARICAMENTO" : "SCARICAMENTO"
|
||||
switch (root.phase) {
|
||||
case "loaded": return "IN MEMORIA"
|
||||
case "generating": return "GENERAZIONE"
|
||||
case "loading": return "CARICAMENTO"
|
||||
case "unloaded": return "SCARICATO"
|
||||
case "stopped": return "SERVIZIO FERMO"
|
||||
case "unreachable": return "NON RAGGIUNGIBILE"
|
||||
default: return "?"
|
||||
}
|
||||
}
|
||||
function phaseBadge() {
|
||||
if (root.pending) return "ATTENDI"
|
||||
switch (root.phase) {
|
||||
case "loaded": return "IDLE"
|
||||
case "generating": return "GEN"
|
||||
case "loading": return "LOAD"
|
||||
case "unloaded": return "OFF"
|
||||
case "stopped": return "STOP"
|
||||
default: return "ERR"
|
||||
}
|
||||
}
|
||||
function stateLine() {
|
||||
const m = root.modelInfo
|
||||
if (root.phase === "generating" && m.tok_s) return "in generazione · " + Number(m.tok_s).toFixed(1) + " t/s"
|
||||
switch (root.phase) {
|
||||
case "loaded": return "caricato, in attesa"
|
||||
case "loading": return "caricamento in corso…"
|
||||
case "unloaded": return "scaricato · VRAM libera"
|
||||
case "stopped": return "container fermo"
|
||||
default: return root.errorMsg.length > 0 ? root.errorMsg : "stato sconosciuto"
|
||||
}
|
||||
}
|
||||
function ctxLabel() {
|
||||
const c = root.modelInfo.ctx
|
||||
if (!c) return "—"
|
||||
return c >= 1024 ? Math.round(c / 1024) + "k" : String(c)
|
||||
}
|
||||
function gib(v) { return (typeof v === "number") ? (v / 1024).toFixed(1) : "—" }
|
||||
function num(v, d) { return (typeof v === "number") ? v : (d || 0) }
|
||||
// valore numerico o NaN (per le barre: NaN -> grigio e barra vuota)
|
||||
function metric(v) { return (typeof v === "number") ? v : NaN }
|
||||
function fmt(v, d, suffix) { return (typeof v === "number") ? v.toFixed(d === undefined ? 0 : d) + (suffix || "") : "n/d" }
|
||||
|
||||
// Colore semaforico di una barra: NaN -> grigio, poi verde/ambra/rosso.
|
||||
function levelColor(value, warnAt, critAt) {
|
||||
if (isNaN(value)) return root.naColor
|
||||
if (value >= critAt) return root.critColor
|
||||
if (value >= warnAt) return root.warnColor
|
||||
return root.okColor
|
||||
}
|
||||
// Frazione 0..1 di riempimento.
|
||||
function fillRatio(value, scaleMax) {
|
||||
if (isNaN(value) || !(scaleMax > 0)) return 0
|
||||
return Math.max(0, Math.min(1, value / scaleMax))
|
||||
}
|
||||
// Per le velocità la semantica è inversa a quella dei carichi: alto = bene.
|
||||
function speedColor(value, nominal) {
|
||||
if (isNaN(value) || value <= 0) return root.naColor
|
||||
if (value >= nominal) return root.okColor
|
||||
if (value >= nominal * 0.5) return root.warnColor
|
||||
return root.critColor
|
||||
}
|
||||
|
||||
// ---------- polling ----------
|
||||
Plasma5Support.DataSource {
|
||||
id: poll
|
||||
engine: "executable"
|
||||
interval: 4000
|
||||
connectedSources: [root.pollSource]
|
||||
onNewData: function (source, data) {
|
||||
const out = ((data && data["stdout"]) || "").trim()
|
||||
if (out.length < 2) { root.errorMsg = "nessuna risposta dal control plane"; root.phase = "unreachable"; return }
|
||||
let o
|
||||
try { o = JSON.parse(out) } catch (e) { root.errorMsg = "risposta non valida"; return }
|
||||
if (o.error) { root.errorMsg = o.error; root.phase = "unreachable"; return }
|
||||
root.errorMsg = ""
|
||||
root.st = o
|
||||
root.phase = o.phase || "unknown"
|
||||
root.lastUpdate = new Date().toLocaleTimeString(Qt.locale(), "HH:mm:ss")
|
||||
root.checkPending()
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- azioni ----------
|
||||
Plasma5Support.DataSource {
|
||||
id: act
|
||||
engine: "executable"
|
||||
connectedSources: []
|
||||
onNewData: function (source, data) { if (source === root.actSource) root.clearAction() }
|
||||
}
|
||||
function clearAction() {
|
||||
if (root.actSource.length > 0) { try { act.disconnectSource(root.actSource) } catch (e) {} }
|
||||
root.actSource = ""
|
||||
}
|
||||
function runAction(cmd, target) {
|
||||
root.counter++
|
||||
root.pending = true
|
||||
root.pendingTarget = target
|
||||
root.pendingTries = 0
|
||||
root.actSource = "/bin/bash " + root.scripts + "action.sh " + cmd + " " + root.counter
|
||||
act.connectSource(root.actSource)
|
||||
pendingTimer.restart()
|
||||
}
|
||||
// criterio autorevole: la fase osservata, non il flag locale
|
||||
function checkPending() {
|
||||
if (!root.pending) return
|
||||
const done = (root.pendingTarget === "load" &&
|
||||
(root.phase === "loaded" || root.phase === "generating"))
|
||||
|| (root.pendingTarget === "unload" &&
|
||||
(root.phase === "unloaded" || root.phase === "stopped"))
|
||||
if (done) { root.pending = false; pendingTimer.stop() }
|
||||
}
|
||||
Timer {
|
||||
id: pendingTimer
|
||||
interval: 1000
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
root.pendingTries++
|
||||
if (root.pendingTries > 240) { // 240 s: il load da freddo puo' durare ~160 s
|
||||
root.pending = false
|
||||
root.errorMsg = "operazione non confermata (timeout)"
|
||||
stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Barra progressiva con colore a soglie: si riempie su value/scaleMax,
|
||||
// colora verde -> ambra -> rosso in base a warnAt/critAt.
|
||||
component MetricBar: RowLayout {
|
||||
id: bar
|
||||
property string label
|
||||
property real value: NaN
|
||||
property real scaleMax: 100
|
||||
property real warnAt: 60
|
||||
property real critAt: 85
|
||||
property string display
|
||||
// opzionale: metriche dove il valore alto è positivo (velocità)
|
||||
property bool higherIsBetter: false
|
||||
property real nominal: 1
|
||||
|
||||
readonly property real ratio: root.fillRatio(bar.value, bar.scaleMax)
|
||||
readonly property color tint: bar.higherIsBetter
|
||||
? root.speedColor(bar.value, bar.nominal)
|
||||
: root.levelColor(bar.value, bar.warnAt, bar.critAt)
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: 5
|
||||
|
||||
PC3.Label {
|
||||
text: bar.label
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 4
|
||||
Layout.preferredWidth: 28
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 5
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 2.5
|
||||
color: Qt.rgba(1, 1, 1, 0.12)
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * bar.ratio
|
||||
height: parent.height
|
||||
radius: 2.5
|
||||
color: bar.tint
|
||||
Behavior on width { NumberAnimation { duration: 300; easing.type: Easing.OutCubic } }
|
||||
Behavior on color { ColorAnimation { duration: 300 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PC3.Label {
|
||||
text: bar.display
|
||||
color: isNaN(bar.value) ? root.naColor : bar.tint
|
||||
font.bold: true
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.preferredWidth: 52
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
compactRepresentation: Component {
|
||||
Item {
|
||||
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||||
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||||
Kirigami.Icon {
|
||||
anchors.fill: parent
|
||||
source: "utilities-system-monitor"
|
||||
color: root.phaseAccent()
|
||||
}
|
||||
MouseArea { anchors.fill: parent; onClicked: Plasmoid.expanded = !Plasmoid.expanded }
|
||||
}
|
||||
}
|
||||
|
||||
fullRepresentation: Component {
|
||||
Item {
|
||||
implicitWidth: 256
|
||||
implicitHeight: 248
|
||||
Layout.preferredWidth: 256
|
||||
Layout.preferredHeight: 248
|
||||
|
||||
ColumnLayout {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: Math.min(parent.width - 16, 300)
|
||||
spacing: 2
|
||||
|
||||
// header
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 6
|
||||
Kirigami.Icon {
|
||||
source: "utilities-system-monitor"
|
||||
color: root.phaseAccent()
|
||||
Layout.preferredWidth: 20
|
||||
Layout.preferredHeight: 20
|
||||
}
|
||||
RowLayout {
|
||||
spacing: 6
|
||||
Layout.fillWidth: true
|
||||
PC3.Label {
|
||||
text: "STRATA"
|
||||
font.bold: true
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize + 1
|
||||
}
|
||||
PC3.Label {
|
||||
text: root.phaseWord()
|
||||
color: root.phaseAccent()
|
||||
font.bold: true
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 8
|
||||
Layout.preferredHeight: 8
|
||||
radius: 4
|
||||
color: root.phaseAccent()
|
||||
}
|
||||
PC3.Button {
|
||||
icon.name: "view-refresh"
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
enabled: !root.pending
|
||||
onClicked: { try { poll.disconnectSource(root.pollSource); poll.connectSource(root.pollSource) } catch (e) {} }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: Qt.rgba(root.phaseAccent().r, root.phaseAccent().g, root.phaseAccent().b, 0.35)
|
||||
}
|
||||
|
||||
PC3.Label {
|
||||
visible: root.errorMsg.length > 0
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
elide: Text.ElideRight
|
||||
color: Kirigami.Theme.neutralTextColor
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3
|
||||
text: root.errorMsg
|
||||
}
|
||||
|
||||
// card GPU
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: gpuBody.implicitHeight + 10
|
||||
radius: 5
|
||||
color: Qt.rgba(0, 0, 0, 0.28)
|
||||
border.width: 1
|
||||
border.color: Qt.rgba(root.themeIndigo.r, root.themeIndigo.g, root.themeIndigo.b, 0.65)
|
||||
|
||||
ColumnLayout {
|
||||
id: gpuBody
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
spacing: 3
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
PC3.Label {
|
||||
text: "GPU"
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3
|
||||
}
|
||||
PC3.Label {
|
||||
text: String(root.gpuInfo.name || "").replace("NVIDIA ", "")
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3
|
||||
}
|
||||
PC3.Label {
|
||||
text: root.speedsLive ? "LIVE" : "ULTIMA"
|
||||
color: root.speedsLive ? root.themeGreen : Kirigami.Theme.disabledTextColor
|
||||
font.bold: true
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 4
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
PC3.Label {
|
||||
text: root.gib(root.gpuInfo.mem_used_mib) + " / " + root.gib(root.gpuInfo.mem_total_mib) + " GiB"
|
||||
font.bold: true
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 2
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 6
|
||||
radius: 3
|
||||
color: Qt.rgba(1, 1, 1, 0.12)
|
||||
Rectangle {
|
||||
width: parent.width * Math.min(1, Math.max(0, root.num(root.gpuInfo.mem_pct, 0) / 100))
|
||||
height: parent.height
|
||||
radius: 3
|
||||
color: root.themeIndigo
|
||||
Behavior on width { NumberAnimation { duration: 250; easing.type: Easing.OutCubic } }
|
||||
}
|
||||
}
|
||||
MetricBar {
|
||||
label: "UTIL"
|
||||
value: root.metric(root.gpuInfo.util_pct)
|
||||
scaleMax: 100
|
||||
warnAt: root.utilWarn
|
||||
critAt: root.utilCrit
|
||||
display: root.fmt(root.gpuInfo.util_pct, 0, "%")
|
||||
}
|
||||
MetricBar {
|
||||
label: "TEMP"
|
||||
value: root.metric(root.gpuInfo.temp_c)
|
||||
scaleMax: 100
|
||||
warnAt: root.tempWarn
|
||||
critAt: root.tempCrit
|
||||
display: root.fmt(root.gpuInfo.temp_c, 0, " °C")
|
||||
}
|
||||
MetricBar {
|
||||
label: "PWR"
|
||||
value: root.metric(root.gpuInfo.power_w)
|
||||
scaleMax: root.gpuPowerMax
|
||||
warnAt: root.gpuPowerMax * root.powerWarnRatio
|
||||
critAt: root.gpuPowerMax * root.powerCritRatio
|
||||
display: root.fmt(root.gpuInfo.power_w, 0, " W")
|
||||
}
|
||||
|
||||
// velocità del motore: decode (generazione) e prefill, tok/s
|
||||
MetricBar {
|
||||
label: "DEC"
|
||||
value: root.decodeTokS
|
||||
scaleMax: root.decodeMax
|
||||
nominal: root.decodeNominal
|
||||
higherIsBetter: true
|
||||
display: root.fmt(root.decodeTokS, 1, " t/s")
|
||||
}
|
||||
MetricBar {
|
||||
label: "PRE"
|
||||
value: root.prefillTokS
|
||||
scaleMax: root.prefillMax
|
||||
nominal: root.prefillNominal
|
||||
higherIsBetter: true
|
||||
display: root.fmt(root.prefillTokS, 0, " t/s")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// card servizio + controllo (load/unload nativo)
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: svcBody.implicitHeight + 10
|
||||
radius: 5
|
||||
color: Qt.rgba(0, 0, 0, 0.28)
|
||||
border.width: 1
|
||||
border.color: Qt.rgba(root.phaseAccent().r, root.phaseAccent().g, root.phaseAccent().b, 0.65)
|
||||
|
||||
ColumnLayout {
|
||||
id: svcBody
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
spacing: 2
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
PC3.Label {
|
||||
text: "SERVIZIO"
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3
|
||||
}
|
||||
PC3.Label {
|
||||
text: root.phaseBadge()
|
||||
color: root.phaseAccent()
|
||||
font.bold: true
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
PC3.BusyIndicator {
|
||||
visible: root.pending
|
||||
running: root.pending
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
||||
}
|
||||
PC3.Switch {
|
||||
checked: root.running
|
||||
enabled: !root.pending && root.phase !== "unreachable" && root.phase !== "stopped"
|
||||
onToggled: {
|
||||
root.runAction(checked ? "load" : "unload", checked ? "load" : "unload")
|
||||
checked = Qt.binding(function () { return root.running })
|
||||
}
|
||||
QQC2.ToolTip.text: root.running ? "Scarica il modello (libera la VRAM)" : "Carica il modello in VRAM"
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: 500
|
||||
}
|
||||
}
|
||||
|
||||
PC3.Label {
|
||||
Layout.fillWidth: true
|
||||
text: root.modelInfo.alias || "—"
|
||||
font.bold: true
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 1
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
PC3.Label {
|
||||
text: (root.modelInfo.kv ? root.modelInfo.kv + " KV" : "—") + " · ctx " + root.ctxLabel()
|
||||
+ " · " + (root.concInfo.serving || 1) + "x"
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
PC3.Label {
|
||||
text: root.modelInfo.vision ? "vision ✓" : "vision —"
|
||||
color: root.modelInfo.vision ? root.themeGreen : Kirigami.Theme.disabledTextColor
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3
|
||||
}
|
||||
}
|
||||
|
||||
PC3.Label {
|
||||
Layout.fillWidth: true
|
||||
text: root.stateLine()
|
||||
color: root.pending ? root.themeViolet : Kirigami.Theme.disabledTextColor
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 4
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
PC3.Label {
|
||||
text: root.lastUpdate ? "SYNC " + root.lastUpdate : "SYNC…"
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
PC3.Label {
|
||||
text: root.pending ? "PENDING" : root.phaseBadge()
|
||||
color: root.phaseAccent()
|
||||
font.bold: true
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize - 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user