import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { execFileSync } from "node:child_process"; import { fileURLToPath } from "node:url"; import path from "node:path"; import { renderGpuGraph, renderInflight, renderUsage } from "../src/lib/render"; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const OUT = path.join(ROOT, "store-assets"); const TMP = path.join(OUT, ".gen"); const FONT = "Helvetica, Arial, sans-serif"; const BG = "#0e1117"; const PANEL = "#161b26"; const STROKE = "#242b38"; const MUTED = "#8b93a5"; const WHITE = "#e6e9ef"; const GREEN = "#3fae5a"; const RED = "#e0453a"; const AMBER = "#d9a02a"; rmSync(TMP, { recursive: true, force: true }); mkdirSync(TMP, { recursive: true }); function convert(src: string, dst: string, w: number, h: number): void { execFileSync("rsvg-convert", ["--unlimited", "-w", String(w), "-h", String(h), "-o", dst, src]); } function rasterizeKey(name: string, svg: string, scale = 3): string { const svgPath = path.join(TMP, `${name}.svg`); const pngPath = path.join(TMP, `${name}.png`); writeFileSync(svgPath, svg); convert(svgPath, pngPath, 72 * scale, 72 * scale); return `${name}.png`; } function svg(w: number, h: number, body: string): string { return ` ${body} `; } function text(content: string, x: number, y: number, size: number, fill: string, weight = "normal", anchor = "middle"): string { return ` ${content}`; } function keyImage(png: string, cx: number, cy: number, size: number): string { return ` `; } function keyWithLabel(png: string, label: string, cx: number, cy: number, size: number): string { return `${keyImage(png, cx, cy, size)} ${text(label, cx, cy + size / 2 + 40, 26, WHITE)}`; } function header(title: string, subtitle: string): string { return `${text(title, 960, 108, 56, WHITE, "bold")} ${text(subtitle, 960, 164, 28, MUTED)}`; } function footer(caption: string): string { return `${text(caption, 960, 872, 24, MUTED)}`; } const keys: Record = { inflightIdle: renderInflight({ modelName: "DeepSeek-V4-Flash-0731", state: "ready", count: 0, offline: false }), inflightActive: renderInflight({ modelName: "DeepSeek-V4-Flash-0731", state: "ready", count: 3, offline: false, history: [0, 1, 2, 3, 2, 3, 4, 3, 3, 2, 3, 2, 1] }), usageP95: renderUsage({ modelName: "DeepSeek-V4-Flash-0731", stats: { totalRequests: 1950, totalInputTokens: 312177540, totalOutputTokens: 889193, genP95: 378.86 }, primaryStat: "gen_p95", offline: false }), usageReq: renderUsage({ modelName: "All Models", stats: { totalRequests: 1985, totalInputTokens: 312327705, totalOutputTokens: 932710, genP95: 378.86 }, primaryStat: "requests", offline: false }), gpuUtil: renderGpuGraph({ gpuName: "GPU 0 · RTX 5090", metric: "util_percent", value: 67, history: [10, 20, 40, 67, 55, 80, 72, 45, 60], offline: false }), gpuPower: renderGpuGraph({ gpuName: "GPU 1 · RTX 5090", metric: "power", value: 312, history: [100, 150, 200, 312, 280, 350, 220, 190, 260], offline: false }), gpuAll: renderGpuGraph({ gpuName: "ALL GPUS", metric: "temperature", value: 63, history: [55, 58, 61, 63, 60, 65, 64, 62, 66], offline: false }), gpuCombo: renderGpuGraph({ gpuName: "GPU 0+2", metric: "util_percent", value: 71, history: [30, 45, 52, 71, 64, 78, 70, 58, 66], offline: false }), }; const pngs: Record = {}; for (const [name, svg] of Object.entries(keys)) { pngs[name] = rasterizeKey(name, svg); } function writePng(name: string, w: number, h: number, body: string): void { const svgPath = path.join(TMP, `${name}.svg`); writeFileSync(svgPath, svg(w, h, body)); convert(svgPath, path.join(OUT, name), w, h); console.log(`wrote store-assets/${name} (${w}x${h})`); } // Thumbnail — 1920x960 { const s = 240; const cx = [1250, 1570]; const cy = [360, 700]; const cols = [ keyWithLabel(pngs.inflightActive, "In-Flight Monitor", cx[0], cy[0], s), keyWithLabel(pngs.usageReq, "Usage stats", cx[1], cy[0], s), keyWithLabel(pngs.gpuUtil, "GPU Graph", cx[0], cy[1], s), keyWithLabel(pngs.gpuCombo, "GPU combination", cx[1], cy[1], s), ]; const bullets = [ ["#3fae5a", "In-flight request counts with a 60-second activity spark"], ["#d9a02a", "Usage stats: requests, tokens, and generation-speed P95"], ["#e0453a", "Live GPU graphs — per GPU, all GPUs, or a combination"], ] .map( ([color, t], i) => ` \n` + text(t, 146, 488 + i * 78, 28, WHITE, "normal", "start"), ) .join("\n"); writePng( "thumbnail.png", 1920, 960, `${text("llama-watch", 116, 250, 96, WHITE, "bold", "start")} ${text("Stream Deck keys for your llama-swap LLM server", 116, 322, 30, MUTED, "normal", "start")} ${bullets} ${cols.join("\n")}`, ); } // Gallery 1 — In-Flight Monitor { const s = 240; const centers = [470, 810, 1150, 1490]; const items = [ [pngs.inflightIdle, "IDLE — no requests"], [pngs.inflightActive, "3 in flight + 60s spark"], [pngs.usageP95, "Usage · gen speed P95"], [pngs.usageReq, "Usage · totals"], ]; writePng( "gallery-1-inflight.png", 1920, 960, `${header("In-Flight Monitor", "Live request counts, a 60-second activity spark, and usage stats")} ${items.map(([png, label], i) => keyWithLabel(png as string, label as string, centers[i], 460, s)).join("\n")} ${footer("Counts update instantly from the SSE feed; the spark shows the last 60 seconds of activity.")}`, ); } // Gallery 2 — GPU Graph { const s = 240; const centers = [470, 810, 1150, 1490]; const items = [ [pngs.gpuUtil, "GPU 0 · utilization"], [pngs.gpuPower, "GPU 1 · power draw"], [pngs.gpuAll, "All GPUs · temperature"], [pngs.gpuCombo, "GPU 0+2 · utilization combo"], ]; writePng( "gallery-2-gpu.png", 1920, 960, `${header("GPU Graph", "Live metric charts — one GPU, all GPUs, or a custom combination")} ${items.map(([png, label], i) => keyWithLabel(png as string, label as string, centers[i], 460, s)).join("\n")} ${footer("Sampled every 5 seconds: utilization, VRAM, temperature, power draw, and fan speed.")}`, ); } // Gallery 3 — Setup { const field = (label: string, value: string, y: number): string => `${text(label, 200, y, 22, MUTED, "normal", "start")} ${text(value, 220, y + 22 + 29, 22, WHITE, "normal", "start")}`; const panel = `${text("In-Flight Monitor settings", 200, 240, 30, WHITE, "bold", "start")} ${field("Base URL", "http://localhost:9292", 300)} ${field("API Key", "Optional", 395)} ${field("Model", "DeepSeek-V4-Flash-0731 (ready)", 490)} ${field("Display", "Usage stats", 585)} ${field("Primary stat", "Generation speed P95", 680)} `; writePng( "gallery-3-setup.png", 1920, 960, `${header("Point it at your llama-swap instance", "Per-key settings in the property inspector")} ${panel} ${keyWithLabel(pngs.usageReq, "Usage stats key", 1420, 430, 300)} ${footer("Every key is independent: model, display mode, GPU, metric, and combination.")}`, ); } // App icon — 288x288 convert(path.join(ROOT, "com.bryce.llamawatch.sdPlugin/imgs/plugin/icon.svg"), path.join(OUT, "app-icon-288.png"), 288, 288); console.log("wrote store-assets/app-icon-288.png (288x288)");