feat: lots 430-434 — /upscale, /count, /speak, emoji health, /flip

Lot 430: /upscale placeholder (needs ESRGAN model)
Lot 431: /count — file counts for all data directories
Lot 432: /speak <persona> <text> — force TTS as any persona
Lot 433: Emoji health indicators in admin dashboard
Lot 434: /flip enhanced (PILE/FACE + emoji)

132 commands, 211 tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
L'électron rare
2026-03-21 23:23:15 +01:00
co-authored by Claude Opus 4.6
parent 088decd1ae
commit 4959539bfa
6 changed files with 47 additions and 5 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
{
"topics": {},
"pins": {},
"savedAt": "2026-03-21T22:17:08.574Z"
"savedAt": "2026-03-21T22:22:08.578Z"
}
+1 -1
View File
@@ -2,5 +2,5 @@
"nick": "Pharmacius",
"facts": [],
"summary": "",
"lastUpdated": "2026-03-21T22:20:02.284Z"
"lastUpdated": "2026-03-21T22:23:01.597Z"
}
+14
View File
@@ -23,6 +23,7 @@ export const CHAT_COMMANDS = new Set([
"/haiku",
"/timer",
"/story",
"/speak",
]);
export function createChatCommandHandler(deps: CommandHandlerDeps) {
@@ -821,6 +822,19 @@ export function createChatCommandHandler(deps: CommandHandlerDeps) {
return;
}
case "/speak": {
// /speak <persona> <texte> — force TTS for text as persona
const speakMatch = text.match(/^\/speak\s+(\S+)\s+(.+)$/);
if (!speakMatch) { send(ws, { type: "system", text: "Usage: /speak <persona> <texte>" }); return; }
const [, speakNick, speakText] = speakMatch;
const persona = getPersonas().find(p => p.nick.toLowerCase() === speakNick.toLowerCase());
if (!persona) { send(ws, { type: "system", text: `Persona "${speakNick}" inconnue.` }); return; }
const { synthesizeTTS } = await import("./ws-multimodal.js");
await synthesizeTTS(persona.nick, speakText, info.channel, broadcast);
return;
}
default:
send(ws, { type: "system", text: `Commande inconnue: ${cmd}. Tape /help.` });
}
+7
View File
@@ -30,6 +30,7 @@ export const GENERATE_COMMANDS = new Set([
"/drone", "/grain", "/circus", "/honk", "/kokoro",
"/variations",
"/imagine-queue",
"/upscale",
]);
export function createGenerateCommandHandler(deps: CommandHandlerDeps) {
@@ -1547,6 +1548,12 @@ export function createGenerateCommandHandler(deps: CommandHandlerDeps) {
return;
}
case "/upscale": {
// /upscale — upscale last generated image 2x (placeholder, needs ESRGAN model)
send(ws, { type: "system", text: "Upscale non implemente (necessite modele ESRGAN). Utilisez /imagine avec un prompt plus detaille." });
return;
}
default:
send(ws, { type: "system", text: `Commande inconnue: ${cmd}. Tape /help.` });
}
+23 -2
View File
@@ -65,6 +65,7 @@ export const INFO_COMMANDS = new Set([
"/about",
"/benchmark",
"/ping",
"/count",
]);
export function createInfoCommandHandler(deps: CommandHandlerDeps) {
@@ -618,8 +619,9 @@ export function createInfoCommandHandler(deps: CommandHandlerDeps) {
}
case "/flip": {
const result = Math.random() < 0.5 ? "pile" : "face";
broadcast(info.channel, { type: "system", text: `\u{1FA99} ${info.nick}: ${result}!` });
const result = Math.random() < 0.5 ? "PILE" : "FACE";
const emoji = result === "PILE" ? "\u{1FA99}" : "\u{1F451}";
broadcast(info.channel, { type: "system", text: `${emoji} ${info.nick} lance une piece: ${result}!` });
return;
}
@@ -629,6 +631,25 @@ export function createInfoCommandHandler(deps: CommandHandlerDeps) {
return;
}
case "/count": {
const fs = await import("node:fs");
const countFiles = (dir: string) => {
try { return fs.readdirSync(path.join(process.cwd(), "data", dir)).filter((f: string) => !f.startsWith(".")).length; } catch { return 0; }
};
const lines = [
"=== COMPTEURS ===",
` Personas: ${getPersonas().length}`,
` Images: ${countFiles("media/images")}`,
` Audio: ${countFiles("media/audio")}`,
` Samples DAW: ${countFiles("daw-samples")}`,
` Compositions: ${countFiles("compositions")}`,
` Exports: ${countFiles("exports")}`,
` Feedback: ${countFiles("feedback")}`,
];
send(ws, { type: "system", text: lines.join("\n") });
return;
}
case "/uptime": {
const secs = Math.floor(process.uptime());
const h = Math.floor(secs / 3600);
+1 -1
View File
@@ -61,7 +61,7 @@ function ServiceHealth() {
className={`admin-health-card ${s.ok ? "admin-health-ok" : "admin-health-fail"}`}
>
<div className="admin-health-name">{name.toUpperCase()}</div>
<div className="admin-health-status">{s.ok ? "\u25CF" : "\u25CB"}</div>
<div className="admin-health-status">{s.ok ? "\u{1F7E2}" : "\u{1F534}"}</div>
{s.detail && <div className="admin-health-detail">{s.detail}</div>}
</div>
))}