Immagini: avanzamento sul volto e nel registro, elenco modelli scaricati; impostazioni con etichette a capo e larghezza contenuta

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Fable 5.1 committed 2026-09-27 20:39:52 +02:00
1 parent 3e9ecd487d
commit a078dff608
5 files changed
+107 -30

No files matched your search

+1 -1
View File
@@ -84,7 +84,7 @@ class Avatar3DView(QWebEngineView):
# ── API thread-safe (valori semplici, letti dal timer sul thread Qt) ── # ── API thread-safe (valori semplici, letti dal timer sul thread Qt) ──
def set_state(self, state: str) -> None: def set_state(self, state: str) -> None:
self._state = str(state or "LISTENING") self._state = str(state or "LISTENING").replace("'", "’")
def set_audio_level(self, level: float) -> None: def set_audio_level(self, level: float) -> None:
try: try:
+31 -13
View File
@@ -45,9 +45,16 @@ def _combo(items, current) -> QComboBox:
c.addItem(label, value) c.addItem(label, value)
idx = c.findData(current) idx = c.findData(current)
c.setCurrentIndex(idx if idx >= 0 else 0) c.setCurrentIndex(idx if idx >= 0 else 0)
# le etichette lunghe non allargano la finestra: il menu si apre comunque a larghezza piena
c.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon); c.setMinimumContentsLength(22)
return c return c
def _note(text: str) -> QLabel:
"""Etichetta descrittiva che va a capo invece di allargare la finestra."""
lbl = QLabel(text); lbl.setWordWrap(True); return lbl
class SettingsDialog(QDialog): class SettingsDialog(QDialog):
_async = pyqtSignal(str, object) _async = pyqtSignal(str, object)
@@ -88,7 +95,7 @@ class SettingsDialog(QDialog):
self.api_key = QLineEdit(); self.api_key.setEchoMode(QLineEdit.EchoMode.Password) self.api_key = QLineEdit(); self.api_key.setEchoMode(QLineEdit.EchoMode.Password)
self.api_key.setPlaceholderText("•••••• (salvata, lascia vuoto per non cambiarla)" if s.get_secret("anthropic_api_key") else "sk-ant-…") self.api_key.setPlaceholderText("•••••• (salvata, lascia vuoto per non cambiarla)" if s.get_secret("anthropic_api_key") else "sk-ant-…")
f.addRow("Chiave API Anthropic", self.api_key) f.addRow("Chiave API Anthropic", self.api_key)
f.addRow("", QLabel("Creala su console.anthropic.com. Viene salvata nel portachiavi di macOS.")) f.addRow("", _note("Creala su console.anthropic.com. Viene salvata nel portachiavi di macOS."))
self.stack.addWidget(w) self.stack.addWidget(w)
# Locale # Locale
w = QWidget(); f = QFormLayout(w) w = QWidget(); f = QFormLayout(w)
@@ -183,7 +190,7 @@ class SettingsDialog(QDialog):
# ── Telegram ────────────────────────────────────────────────────── # ── Telegram ──────────────────────────────────────────────────────
form3 = QFormLayout() form3 = QFormLayout()
form3.addRow(QLabel("Telegram (account personale): credenziali da my.telegram.org > API development tools")) form3.addRow(_note("Telegram (account personale): credenziali da my.telegram.org > API development tools"))
row = QHBoxLayout() row = QHBoxLayout()
self.tg_id = QLineEdit(str(s.get("telegram_api_id") or "")); self.tg_id.setPlaceholderText("api id"); row.addWidget(self.tg_id) self.tg_id = QLineEdit(str(s.get("telegram_api_id") or "")); self.tg_id.setPlaceholderText("api id"); row.addWidget(self.tg_id)
self.tg_hash = QLineEdit(); self.tg_hash.setEchoMode(QLineEdit.EchoMode.Password) self.tg_hash = QLineEdit(); self.tg_hash.setEchoMode(QLineEdit.EchoMode.Password)
@@ -207,7 +214,7 @@ class SettingsDialog(QDialog):
# ── WhatsApp (dispositivo collegato, Baileys) ───────────────────── # ── WhatsApp (dispositivo collegato, Baileys) ─────────────────────
form4 = QFormLayout() form4 = QFormLayout()
form4.addRow(QLabel("WhatsApp in tempo reale (client non ufficiale: possibile blocco del numero, a tuo rischio)")) form4.addRow(_note("WhatsApp in tempo reale (client non ufficiale: possibile blocco del numero, a tuo rischio)"))
row = QHBoxLayout() row = QHBoxLayout()
self.wa_live = QCheckBox("Attivo all'avvio"); self.wa_live.setChecked(bool(s.get("whatsapp_live"))); row.addWidget(self.wa_live) self.wa_live = QCheckBox("Attivo all'avvio"); self.wa_live.setChecked(bool(s.get("whatsapp_live"))); row.addWidget(self.wa_live)
self.wa_annuncia = QCheckBox("Annuncia a voce i messaggi in arrivo"); self.wa_annuncia.setChecked(bool(s.get("whatsapp_annuncia"))); row.addWidget(self.wa_annuncia) self.wa_annuncia = QCheckBox("Annuncia a voce i messaggi in arrivo"); self.wa_annuncia.setChecked(bool(s.get("whatsapp_annuncia"))); row.addWidget(self.wa_annuncia)
@@ -224,7 +231,7 @@ class SettingsDialog(QDialog):
# ── Assistenza server (bot sysadmin + monitoraggio Sentinella) ──── # ── Assistenza server (bot sysadmin + monitoraggio Sentinella) ────
form_srv = QFormLayout() form_srv = QFormLayout()
form_srv.addRow(QLabel("Assistenza server: bot Telegram sysadmin e monitoraggio Sentinella sul server ponte (accesso SSH con chiave)")) form_srv.addRow(_note("Assistenza server: bot Telegram sysadmin e monitoraggio Sentinella sul server ponte (accesso SSH con chiave)"))
self.srv_ssh = QLineEdit(str(s.get("server_assist_ssh") or "")); self.srv_ssh.setPlaceholderText("root@host del server ponte") self.srv_ssh = QLineEdit(str(s.get("server_assist_ssh") or "")); self.srv_ssh.setPlaceholderText("root@host del server ponte")
form_srv.addRow("SSH server ponte", self.srv_ssh) form_srv.addRow("SSH server ponte", self.srv_ssh)
self.srv_dir = QLineEdit(str(s.get("server_assist_dir") or "")); self.srv_dir.setPlaceholderText("/opt/aiserverassistance") self.srv_dir = QLineEdit(str(s.get("server_assist_dir") or "")); self.srv_dir.setPlaceholderText("/opt/aiserverassistance")
@@ -250,7 +257,7 @@ class SettingsDialog(QDialog):
# ── Casa (Home Assistant) ───────────────────────────────────────── # ── Casa (Home Assistant) ─────────────────────────────────────────
form_ha = QFormLayout() form_ha = QFormLayout()
form_ha.addRow(QLabel("Home Assistant: crea un token di accesso a lunga durata dal tuo profilo (Sicurezza) e incollalo qui. Viene salvato nel portachiavi.")) form_ha.addRow(_note("Home Assistant: crea un token di accesso a lunga durata dal tuo profilo (Sicurezza) e incollalo qui. Viene salvato nel portachiavi."))
self.ha_url = QLineEdit(str(s.get("homeassistant_url") or "")); self.ha_url.setPlaceholderText("http://homeassistant.local:8123") self.ha_url = QLineEdit(str(s.get("homeassistant_url") or "")); self.ha_url.setPlaceholderText("http://homeassistant.local:8123")
form_ha.addRow("Indirizzo", self.ha_url) form_ha.addRow("Indirizzo", self.ha_url)
row = QHBoxLayout() row = QHBoxLayout()
@@ -265,30 +272,40 @@ class SettingsDialog(QDialog):
from PyQt6.QtWidgets import QPlainTextEdit from PyQt6.QtWidgets import QPlainTextEdit
from .mcp_client import CONFIG_FILE as MCP_FILE, manager as mcp_manager from .mcp_client import CONFIG_FILE as MCP_FILE, manager as mcp_manager
form_mcp = QFormLayout() form_mcp = QFormLayout()
form_mcp.addRow(QLabel("Server MCP esterni: stesso formato di Claude Desktop (mcpServers con command/args/env, oppure url/headers). " form_mcp.addRow(_note("Server MCP esterni: stesso formato di Claude Desktop (mcpServers con command/args/env, oppure url/headers). "
"Gli strumenti diventano disponibili a tutti i motori; con Claude Code i server vengono passati direttamente a claude.")) "Gli strumenti diventano disponibili a tutti i motori; con Claude Code i server vengono passati direttamente a claude."))
self.mcp_text = QPlainTextEdit() self.mcp_text = QPlainTextEdit()
try: try:
self.mcp_text.setPlainText(MCP_FILE.read_text(encoding="utf-8")) self.mcp_text.setPlainText(MCP_FILE.read_text(encoding="utf-8"))
except Exception: except Exception:
self.mcp_text.setPlainText('{\n "mcpServers": {\n }\n}\n') self.mcp_text.setPlainText('{\n "mcpServers": {\n }\n}\n')
self.mcp_text.setMinimumHeight(220) self.mcp_text.setMinimumHeight(180); self.mcp_text.setMaximumHeight(260)
self.mcp_text.setLineWrapMode(QPlainTextEdit.LineWrapMode.WidgetWidth)
form_mcp.addRow(self.mcp_text) form_mcp.addRow(self.mcp_text)
row = QHBoxLayout() row = QHBoxLayout()
b = QPushButton("Applica e ricollega"); b.clicked.connect(self._mcp_apply); row.addWidget(b) b = QPushButton("Applica e ricollega"); b.clicked.connect(self._mcp_apply); row.addWidget(b)
b = QPushButton("Esempio"); b.clicked.connect(self._mcp_example); row.addWidget(b) b = QPushButton("Esempio"); b.clicked.connect(self._mcp_example); row.addWidget(b)
row.addStretch() row.addStretch()
form_mcp.addRow(row) form_mcp.addRow(row)
self.mcp_hint = QLabel(mcp_manager.status()); self.mcp_hint.setWordWrap(True); form_mcp.addRow(self.mcp_hint) self.mcp_hint = QLabel(mcp_manager.status()); self.mcp_hint.setWordWrap(True); self.mcp_hint.setMinimumWidth(200); form_mcp.addRow(self.mcp_hint)
add(pg_tool, form_mcp) add(pg_tool, form_mcp)
# ── Immagini (mflux) ────────────────────────────────────────────── # ── Immagini (mflux) ──────────────────────────────────────────────
form_img = QFormLayout() form_img = QFormLayout()
form_img.addRow(QLabel("Generazione immagini in locale (mflux, MLX). Il primo uso scarica il modello da Hugging Face.")) form_img.addRow(_note("Generazione immagini in locale (mflux, MLX). Il primo uso scarica il modello da Hugging Face."))
self.img_fam = _combo([("z-image-turbo", "Z-Image Turbo (veloce, 8 passi, consigliato)"), ("schnell", "FLUX.1 schnell (4 passi)"), ("dev", "FLUX.1 dev (lento, licenza non commerciale)"), ("qwen", "Qwen-Image (pesante, ottimo col testo)")], s.get("immagini_famiglia") or "z-image-turbo") self.img_fam = _combo([("z-image-turbo", "Z-Image Turbo (veloce, 8 passi, consigliato)"), ("schnell", "FLUX.1 schnell (4 passi)"), ("dev", "FLUX.1 dev (lento, licenza non commerciale)"), ("qwen", "Qwen-Image (pesante, ottimo col testo)")], s.get("immagini_famiglia") or "z-image-turbo")
form_img.addRow("Famiglia", self.img_fam) form_img.addRow("Famiglia", self.img_fam)
self.img_model = QLineEdit(str(s.get("immagini_modello") or "")); self.img_model.setPlaceholderText("vuoto = predefinito (es. mflux-community/z-image-turbo-mflux-q4)") self.img_model = QComboBox(); self.img_model.setEditable(True)
form_img.addRow("Modello (repo Hugging Face)", self.img_model) try:
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "plugins"))
import importlib
self.img_model.addItems([""] + importlib.import_module("immagini").cached_image_models())
except Exception:
self.img_model.addItem("")
self.img_model.setEditText(str(s.get("immagini_modello") or ""))
self.img_model.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon); self.img_model.setMinimumContentsLength(30)
self.img_model.lineEdit().setPlaceholderText("vuoto = predefinito della famiglia; un nome nuovo viene scaricato al primo uso")
form_img.addRow("Modello (elenco: già scaricati)", self.img_model)
add(pg_tool, form_img) add(pg_tool, form_img)
btns = QHBoxLayout(); btns.addStretch() btns = QHBoxLayout(); btns.addStretch()
@@ -299,7 +316,8 @@ class SettingsDialog(QDialog):
outer.addLayout(btns) outer.addLayout(btns)
screen = QApplication.primaryScreen().availableGeometry() if QApplication.primaryScreen() else None screen = QApplication.primaryScreen().availableGeometry() if QApplication.primaryScreen() else None
h = int(screen.height() * 0.85) if screen else 800 h = int(screen.height() * 0.85) if screen else 800
self.resize(820, min(900, h)) w = min(1000, int(screen.width() * 0.9)) if screen else 1000
self.resize(w, min(900, h))
self._async.connect(self._on_async) self._async.connect(self._on_async)
self._check_cc() self._check_cc()
@@ -569,7 +587,7 @@ class SettingsDialog(QDialog):
"server_assist_dir": self.srv_dir.text().strip() or "/opt/aiserverassistance", "server_assist_dir": self.srv_dir.text().strip() or "/opt/aiserverassistance",
"server_assist_bot": self.srv_bot.text().strip().lstrip("@") or "luzaserver_bot", "server_assist_bot": self.srv_bot.text().strip().lstrip("@") or "luzaserver_bot",
"homeassistant_url": self.ha_url.text().strip().rstrip("/"), "homeassistant_url": self.ha_url.text().strip().rstrip("/"),
"immagini_famiglia": self.img_fam.currentData() or "z-image-turbo", "immagini_modello": self.img_model.text().strip(), "immagini_famiglia": self.img_fam.currentData() or "z-image-turbo", "immagini_modello": self.img_model.currentText().strip(),
} }
if self.ha_token.text().strip(): if self.ha_token.text().strip():
values["homeassistant_token"] = self.ha_token.text().strip() values["homeassistant_token"] = self.ha_token.text().strip()
+3 -3
View File
@@ -194,7 +194,7 @@ function drawOverlay() {
levels.push(dispLevel); levels.shift(); levels.push(dispLevel); levels.shift();
const col = COLORS[state.name] || '#00d4ff'; const col = COLORS[state.name] || '#00d4ff';
stateEl.style.color = col; stateEl.style.textShadow = `0 0 8px ${col}88`; stateEl.style.color = col; stateEl.style.textShadow = `0 0 8px ${col}88`;
stateEl.textContent = '● ' + state.name; stateEl.textContent = '● ' + (state.label || state.name);
wctx.clearRect(0, 0, wave.width, wave.height); wctx.clearRect(0, 0, wave.width, wave.height);
const n = levels.length, bw = wave.width / n; const n = levels.length, bw = wave.width / n;
for (let i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
@@ -206,8 +206,8 @@ function drawOverlay() {
} }
window.avatar3d = { window.avatar3d = {
update(level, open, width, name) { state.level = level; state.open = open; state.width = width; if (name) state.name = name; }, update(level, open, width, name) { state.level = level; state.open = open; state.width = width; if (name) { state.label = name; state.name = name.split(' ')[0]; } },
setState(name) { state.name = name; }, setState(name) { state.label = name; state.name = name.split(' ')[0]; },
glance(dx, dy, hold) { smooth.glanceX = dx * 0.3; smooth.glanceY = -dy * 0.2; smooth.glanceUntil = clock.elapsedTime + (hold || 1.1); }, glance(dx, dy, hold) { smooth.glanceX = dx * 0.3; smooth.glanceY = -dy * 0.2; smooth.glanceUntil = clock.elapsedTime + (hold || 1.1); },
setFraming(f) { state.framing = f; frame(); }, setFraming(f) { state.framing = f; frame(); },
status() { return { loaded: Boolean(model), animation: Boolean(mixer), head: Boolean(head), blendShapes: mouth.hasShapes, morphs: morphMeshes.length, eyes: eyes.length }; }, status() { return { loaded: Boolean(model), animation: Boolean(mixer), head: Boolean(head), blendShapes: mouth.hasShapes, morphs: morphMeshes.length, eyes: eyes.length }; },
+65 -6
View File
@@ -27,6 +27,19 @@ FORMATI = {"quadrata": (1024, 1024), "orizzontale": (1280, 768), "verticale": (7
_last: dict = {"path": ""} _last: dict = {"path": ""}
def cached_image_models() -> list[str]:
"""Modelli per immagini già presenti nella cache di Hugging Face (mflux, FLUX, Z-Image, Qwen-Image)."""
hub = Path.home() / ".cache" / "huggingface" / "hub"
out = []
if hub.exists():
for d in sorted(hub.glob("models--*")):
name = d.name[len("models--"):].replace("--", "/")
low = name.lower()
if any(k in low for k in ("mflux", "flux", "z-image", "qwen-image")) and (d / "snapshots").exists():
out.append(name)
return out
def _cfg() -> tuple[str, str]: def _cfg() -> tuple[str, str]:
s = Settings() s = Settings()
fam = str(s.get("immagini_famiglia") or "z-image-turbo") fam = str(s.get("immagini_famiglia") or "z-image-turbo")
@@ -34,6 +47,55 @@ def _cfg() -> tuple[str, str]:
return fam, repo return fam, repo
def _progress(ctx: dict, text: str) -> None:
"""Mostra l'avanzamento sul volto (stato del HUD) e nel registro."""
player = ctx.get("player")
if player is not None and hasattr(player, "set_state"):
try:
player.set_state(f"PROCESSING · {text}")
except Exception:
pass
if ctx.get("log"):
ctx["log"](f"[immagini] {text}")
def _run_with_progress(cmd: list[str], steps: int, ctx: dict, timeout: int = 1800):
"""Esegue mflux leggendo la barra di avanzamento (download dei pesi e passi di diffusione)."""
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=0)
buf, lines, last, t0 = "", [], "", time.time()
_progress(ctx, "immagine: avvio")
try:
while True:
ch = proc.stdout.read(1)
if not ch:
break
if time.time() - t0 > timeout:
proc.kill()
return None, "", ""
if ch in ("\r", "\n"):
line = buf.strip(); buf = ""
if not line:
continue
lines.append(line)
m = re.search(r"Fetching (\d+) files:\s*(\d+)%", line)
if m:
msg = f"download modello {m.group(2)}%"
elif (m := re.search(r"(\d+)/(\d+)\s*\[", line)):
k, n = int(m.group(1)), int(m.group(2))
msg = f"immagine {k}/{n}" if k < n else "immagine: salvataggio"
else:
continue
if msg != last:
last = msg
_progress(ctx, msg)
else:
buf += ch
finally:
proc.wait()
tail = " | ".join(l for l in lines[-3:] if "it/s" not in l and "s/it" not in l)
return proc, tail, ""
def genera(params: dict, ctx: dict) -> str: def genera(params: dict, ctx: dict) -> str:
prompt = str(params.get("descrizione", "")).strip() prompt = str(params.get("descrizione", "")).strip()
if not prompt: if not prompt:
@@ -61,16 +123,13 @@ def genera(params: dict, ctx: dict) -> str:
if ctx.get("log"): if ctx.get("log"):
ctx["log"](f"[immagini] {fam} {w}x{h} {steps} passi: {prompt[:80]}") ctx["log"](f"[immagini] {fam} {w}x{h} {steps} passi: {prompt[:80]}")
t = time.time() t = time.time()
try: proc, tail, err = _run_with_progress(cmd, steps, ctx)
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=1800) if proc is None:
except subprocess.TimeoutExpired:
return "Generazione interrotta: troppo lenta (oltre 30 minuti)." return "Generazione interrotta: troppo lenta (oltre 30 minuti)."
if proc.returncode != 0 or not out.exists(): if proc.returncode != 0 or not out.exists():
err = (proc.stderr or proc.stdout).strip().splitlines()
tail = " | ".join(err[-3:]) if err else f"codice {proc.returncode}"
if "not found" in tail.lower() or "401" in tail or "403" in tail: if "not found" in tail.lower() or "401" in tail or "403" in tail:
tail += " (controlla il nome del modello nelle impostazioni, o accetta la licenza su Hugging Face)" tail += " (controlla il nome del modello nelle impostazioni, o accetta la licenza su Hugging Face)"
return f"Generazione fallita: {tail[:400]}" return f"Generazione fallita: {tail[:400] or f'codice {proc.returncode}'}"
_last["path"] = str(out) _last["path"] = str(out)
secs = time.time() - t secs = time.time() - t
if params.get("mostra", True) not in (False, "false", "no"): if params.get("mostra", True) not in (False, "false", "no"):
+7 -7
View File
@@ -632,7 +632,7 @@ class HudCanvas(QWidget):
# either way because the animation state keeps stepping at 60 Hz. # either way because the animation state keeps stepping at 60 Hz.
self._paint_tick = (self._paint_tick + 1) % 6 self._paint_tick = (self._paint_tick + 1) % 6
active = (self.speaking or amp > 0.02 active = (self.speaking or amp > 0.02
or self.state in ("THINKING", "PROCESSING")) or self.state.split(" ")[0] in ("THINKING", "PROCESSING"))
if _blinked or (self._paint_tick % 2 == 0 if active if _blinked or (self._paint_tick % 2 == 0 if active
else self._paint_tick % 3 == 0): else self._paint_tick % 3 == 0):
# Nothing is on screen when the window is hidden or minimised, so # Nothing is on screen when the window is hidden or minimised, so
@@ -665,9 +665,9 @@ class HudCanvas(QWidget):
return qcol(C.MUTED_C), qcol(C.MUTED_C) return qcol(C.MUTED_C), qcol(C.MUTED_C)
if self.speaking: if self.speaking:
return qcol(C.PRI), qcol(C.ACC) return qcol(C.PRI), qcol(C.ACC)
if self.state in ("THINKING", "PROCESSING"): if self.state.split(" ")[0] in ("THINKING", "PROCESSING"):
return qcol(C.PRI), qcol(C.ACC2) return qcol(C.PRI), qcol(C.ACC2)
if self.state == "LISTENING": if self.state.split(" ")[0] == "LISTENING":
return qcol(C.PRI), qcol(C.GREEN) return qcol(C.PRI), qcol(C.GREEN)
return qcol(C.PRI), qcol(C.PRI_DIM) return qcol(C.PRI), qcol(C.PRI_DIM)
@@ -763,7 +763,7 @@ class HudCanvas(QWidget):
# 6. Sweeping arcs. Long spans, not dashes — the original's grandeur # 6. Sweeping arcs. Long spans, not dashes — the original's grandeur
# came from a few big strokes. Speed is the state: idle drifts, # came from a few big strokes. Speed is the state: idle drifts,
# thinking hurries, speaking runs. # thinking hurries, speaking runs.
rate = 1.0 + (1.9 if self.state in ("THINKING", "PROCESSING") else 0.0) \ rate = 1.0 + (1.9 if self.state.split(" ")[0] in ("THINKING", "PROCESSING") else 0.0) \
+ (1.2 if self.speaking else 0.0) + (1.2 if self.speaking else 0.0)
for k, (rr, span, count, dirn, col, a, wid) in enumerate(( for k, (rr, span, count, dirn, col, a, wid) in enumerate((
(0.955, 118, 2, +1, acc, 0.75, 2.0), (0.955, 118, 2, +1, acc, 0.75, 2.0),
@@ -852,9 +852,9 @@ class HudCanvas(QWidget):
_main = qcol(C.PRI) _main = qcol(C.PRI)
if self.speaking: if self.speaking:
_acc = qcol(C.ACC) _acc = qcol(C.ACC)
elif self.state in ("THINKING", "PROCESSING"): elif self.state.split(" ")[0] in ("THINKING", "PROCESSING"):
_acc = qcol(C.ACC2) _acc = qcol(C.ACC2)
elif self.state == "LISTENING": elif self.state.split(" ")[0] == "LISTENING":
_acc = qcol(C.GREEN) _acc = qcol(C.GREEN)
else: else:
_acc = qcol(C.PRI) _acc = qcol(C.PRI)
@@ -882,7 +882,7 @@ class HudCanvas(QWidget):
elif self.state == "PROCESSING": elif self.state == "PROCESSING":
sym = "▷" if self._blink else "▶" sym = "▷" if self._blink else "▶"
txt, col = f"{sym} PROCESSING", qcol(C.ACC2) txt, col = f"{sym} PROCESSING", qcol(C.ACC2)
elif self.state == "LISTENING": elif self.state.split(" ")[0] == "LISTENING":
sym = "●" if self._blink else "○" sym = "●" if self._blink else "○"
txt, col = f"{sym} LISTENING", qcol(C.GREEN) txt, col = f"{sym} LISTENING", qcol(C.GREEN)
else: else: