From 8ec1c040b2da1e60b61dc9634d80adf4e2aab9b6 Mon Sep 17 00:00:00 2001
From: Crowvic <168686597+MattModeCode@users.noreply.github.com>
Date: Wed, 9 Sep 2026 00:16:02 -0400
Subject: [PATCH 1/2] feat(studio): rebuild the interface as a single-voice
Liquid Glass app
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The Studio was a two-column web page in bundled mono type on a flat dark
field — functional, but it read as a local dev tool rather than a Mac app,
and it made you pick a voice every time despite there only ever being one
that mattered.
Interface
- Single column built around one voice. It is resolved at boot and never
appears as a choice on the composing surface; enrolment, switching and
the environment report moved into a Settings sheet.
- Progressive disclosure: script and Generate at rest, delivery controls
and prosody marks behind panes that summarize themselves when closed.
- Apple Liquid Glass material — layered translucency with backdrop blur
and saturation, specular top lip, depth shadows — over an ambient colour
field that gives the glass something to refract.
- Light and dark are both first class, each with its own glass and ink
values rather than one palette dimmed. Increased contrast drops the
translucency entirely and has its own dark variant.
- System UI type for prose, mono kept only for numerics. The bundled
JetBrains Mono files are gone; nothing referenced them any more.
- ⌘↵ generates. Reduced motion, focus rings and labelled controls throughout.
Structure
- static/ split into tokens.css (design system), style.css (components),
and ES modules api.js / format.js / app.js.
- The eight /api/* routes are untouched; this is a front-end change.
Desktop shell
- The window painted pywebview's white default before the page loaded,
which flashed on the way into a dark interface. It now launches on the
interface's own canvas colour, chosen from the macOS appearance, with a
test asserting the two files cannot drift apart.
Verified in the browser, in the pywebview window, and in a rebuilt
dist/Double Chin.app; 110 tests pass.
Claude-Session: https://claude.ai/code/session_01FdWprv4TKfVzapYFJnRffz
---
README.md | 13 +-
src/double_chin/desktop.py | 41 +-
src/double_chin/studio/static/api.js | 34 +
src/double_chin/studio/static/app.js | 543 +++++++++-----
.../static/fonts/JetBrainsMono-Medium.ttf | Bin 270316 -> 0 bytes
.../static/fonts/JetBrainsMono-Regular.ttf | Bin 270224 -> 0 bytes
src/double_chin/studio/static/format.js | 57 ++
src/double_chin/studio/static/index.html | 294 +++++---
src/double_chin/studio/static/style.css | 683 +++++++++++++-----
src/double_chin/studio/static/tokens.css | 203 ++++++
tests/test_desktop.py | 38 +-
11 files changed, 1429 insertions(+), 477 deletions(-)
create mode 100644 src/double_chin/studio/static/api.js
delete mode 100644 src/double_chin/studio/static/fonts/JetBrainsMono-Medium.ttf
delete mode 100644 src/double_chin/studio/static/fonts/JetBrainsMono-Regular.ttf
create mode 100644 src/double_chin/studio/static/format.js
create mode 100644 src/double_chin/studio/static/tokens.css
diff --git a/README.md b/README.md
index f5033b7..379aab2 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Your voice, on script.
-Double Chin is a local voice-cloning application. Give it a few minutes of someone's voice and a text script, and it reads the script aloud in that voice — entirely on your machine. No cloud, no account, no audio leaving the laptop. One command opens the app: pick a voice, paste a script, hit Generate, and watch it synthesize chunk by chunk with a live speaker-similarity verdict on every take.
+Double Chin is a local voice-cloning application. Give it a few minutes of someone's voice and a text script, and it reads the script aloud in that voice — entirely on your machine. No cloud, no account, no audio leaving the laptop. One command opens a desktop window built around your own voice: write a script, hit Generate, and watch it synthesize chunk by chunk with a speaker-similarity check on every take.

@@ -40,9 +40,18 @@ uv venv --python 3.12 .venv
uv pip install --python .venv/bin/python -e ".[dev]"
source .venv/bin/activate
double-chin doctor # checks device, deps, disk — everything should PASS
-double-chin studio # starts http://127.0.0.1:8787 and opens your browser
+double-chin app # opens the desktop window
```
+`double-chin app` is the desktop shell. `double-chin studio` serves the same
+interface at http://127.0.0.1:8787 in a browser, and `bash packaging/build_app.sh`
+builds `dist/Double Chin.app` so it launches from the Dock like any other app.
+
+The window is built around a single voice — the one you enrolled. Delivery
+controls, prosody marks and the environment report are there when you want them
+and out of the way when you don't; enrolling or switching voices lives in
+Settings.
+
## Clone your own voice
Record the [79-take corpus](recording-scripts/) (~50 minutes), then:
diff --git a/src/double_chin/desktop.py b/src/double_chin/desktop.py
index 73c7cfa..f0ede97 100644
--- a/src/double_chin/desktop.py
+++ b/src/double_chin/desktop.py
@@ -11,6 +11,7 @@
from __future__ import annotations
import socket
+import subprocess
import sys
import threading
import time
@@ -18,10 +19,43 @@
import uvicorn
_PREFERRED_PORT = 8787
+# The window paints its background colour before the page has loaded. Left at
+# pywebview's white default that is a bright flash on the way into a dark
+# interface, so both values below mirror `--canvas` in studio/static/tokens.css
+# — a test asserts they stay in step.
+_LAUNCH_COLOR_LIGHT = "#E9E8F0"
+_LAUNCH_COLOR_DARK = "#14141D"
+_APPEARANCE_TIMEOUT_SECONDS = 2.0
_STARTUP_TIMEOUT_SECONDS = 15.0
_STARTUP_POLL_SECONDS = 0.05
+def _macos_prefers_dark() -> bool:
+ """True when macOS is in dark mode.
+
+ `defaults read -g AppleInterfaceStyle` prints "Dark" in dark mode and exits
+ non-zero in light mode (the key is absent), so any failure means light.
+ """
+ if sys.platform != "darwin":
+ return False
+ try:
+ result = subprocess.run(
+ ["defaults", "read", "-g", "AppleInterfaceStyle"],
+ capture_output=True,
+ text=True,
+ timeout=_APPEARANCE_TIMEOUT_SECONDS,
+ check=False,
+ )
+ except (OSError, subprocess.SubprocessError):
+ return False
+ return result.stdout.strip() == "Dark"
+
+
+def _launch_background_color(prefers_dark=_macos_prefers_dark) -> str:
+ """The colour the window shows in the moment before the page paints."""
+ return _LAUNCH_COLOR_DARK if prefers_dark() else _LAUNCH_COLOR_LIGHT
+
+
def _pick_port() -> int:
"""Prefer the well-known Studio port; fall back to any free loopback port."""
for port in (_PREFERRED_PORT, 0):
@@ -114,9 +148,10 @@ def run() -> int:
window = webview.create_window(
"Double Chin",
f"http://127.0.0.1:{port}",
- width=1160,
- height=780,
- min_size=(760, 560),
+ width=1000,
+ height=820,
+ min_size=(560, 520),
+ background_color=_launch_background_color(),
)
window.events.closed += lambda: setattr(server, "should_exit", True)
diff --git a/src/double_chin/studio/static/api.js b/src/double_chin/studio/static/api.js
new file mode 100644
index 0000000..4a05e36
--- /dev/null
+++ b/src/double_chin/studio/static/api.js
@@ -0,0 +1,34 @@
+/* Thin wrapper over the Studio HTTP API. No endpoint shapes are invented
+ * here — every path below already exists in studio/app.py. */
+"use strict";
+
+async function request(url, options) {
+ const response = await fetch(url, options);
+ if (!response.ok) {
+ let detail = `${response.status} ${response.statusText}`;
+ try {
+ const body = await response.json();
+ if (body.detail) detail = body.detail;
+ } catch (_) { /* error body was not JSON; keep the status line */ }
+ const error = new Error(detail);
+ error.status = response.status;
+ throw error;
+ }
+ return response.json();
+}
+
+export const api = {
+ listVoices: () => request("/api/voices"),
+ enrollVoice: (formData) => request("/api/voices", { method: "POST", body: formData }),
+ createJob: (payload) =>
+ request("/api/jobs", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(payload),
+ }),
+ listHistory: () => request("/api/history"),
+ doctor: () => request("/api/doctor"),
+ jobEvents: (jobId) => new EventSource(`/api/jobs/${jobId}/events`),
+ audioUrl: (jobId, download = false) =>
+ `/api/audio/${jobId}${download ? "?download=true" : ""}`,
+};
diff --git a/src/double_chin/studio/static/app.js b/src/double_chin/studio/static/app.js
index 33c1f75..9628e7d 100644
--- a/src/double_chin/studio/static/app.js
+++ b/src/double_chin/studio/static/app.js
@@ -1,130 +1,264 @@
-/* Double Chin frontend. Plain JS, no build step, no external requests. */
+/* Double Chin — single-voice studio.
+ *
+ * The app is built around one enrolled voice: it is resolved once at boot and
+ * never appears as a choice on the main surface. Voice management, enrolment
+ * and environment facts all live in the Settings sheet.
+ */
"use strict";
+import { api } from "./api.js";
+import {
+ estimateAudioSeconds,
+ estimateWallSeconds,
+ formatClock,
+ formatCount,
+ formatDuration,
+ formatWhen,
+ pluralize,
+ verdictTone,
+} from "./format.js";
+
+const DELIVERY_DEFAULTS = Object.freeze({
+ exaggeration: 0.5,
+ cfg: 0.5,
+ temperature: 0.8,
+ rate: 1,
+});
+
+const KNOB_LABELS = Object.freeze({
+ exaggeration: "Expression",
+ cfg: "Adherence",
+ temperature: "Variation",
+ rate: "Rate",
+});
+
+/* Which enrolled voice is "yours". Only ever set from the Settings sheet —
+ * the composing surface never asks. */
+const VOICE_KEY = "double-chin.voice";
+
const $ = (id) => document.getElementById(id);
+function readStoredVoice() {
+ try {
+ return localStorage.getItem(VOICE_KEY);
+ } catch (_) {
+ return null; // private window, or site data blocked
+ }
+}
+
+function storeVoice(name) {
+ try {
+ localStorage.setItem(VOICE_KEY, name);
+ } catch (_) { /* the choice simply does not persist */ }
+}
+
const state = {
- voices: [],
+ voice: null,
jobRunning: false,
eventSource: null,
timerHandle: null,
jobStartedAt: null,
};
-/* ---------- bootstrap ---------- */
+/* ---------- boot ---------- */
async function init() {
wireControls();
- await Promise.all([refreshVoices(), refreshDoctor(), refreshHistory()]);
+ syncDelivery();
+ updateScriptStats();
+ await Promise.allSettled([refreshVoice(), refreshEnvironment(), refreshHistory()]);
}
-async function fetchJson(url, options) {
- const response = await fetch(url, options);
- if (!response.ok) {
- let detail = `${response.status} ${response.statusText}`;
- try {
- const body = await response.json();
- if (body.detail) detail = body.detail;
- } catch (_) { /* non-JSON error body */ }
- throw new Error(detail);
- }
- return response.json();
+/* ---------- voice ---------- */
+
+async function refreshVoice(preferName) {
+ let voices = [];
+ try {
+ voices = await api.listVoices();
+ } catch (_) { /* leave the app in its no-voice state */ }
+
+ const wanted = preferName ?? readStoredVoice();
+ state.voice = voices.find((voice) => voice.name === wanted) ?? voices[0] ?? null;
+ if (state.voice) storeVoice(state.voice.name);
+
+ renderVoicePicker(voices);
+ renderVoiceCard();
+ renderIdle();
+ updateGenerateEnabled();
}
-/* ---------- voices ---------- */
-
-async function refreshVoices(selectName) {
- state.voices = await fetchJson("/api/voices");
- const select = $("voice");
- select.innerHTML = "";
- for (const voice of state.voices) {
- const option = document.createElement("option");
- option.value = voice.name;
- option.textContent = `${voice.name} · ${voice.duration_seconds}s`;
- select.appendChild(option);
+/** Shown only when more than one voice is enrolled; the main surface never has it. */
+function renderVoicePicker(voices) {
+ const wrapper = $("voice-switch");
+ const select = $("voice-select");
+ wrapper.hidden = voices.length < 2;
+ if (wrapper.hidden) return;
+
+ select.textContent = "";
+ for (const voice of voices) {
+ const option = el("option", { value: voice.name, text: voice.name });
+ if (voice.name === state.voice?.name) option.selected = true;
+ select.append(option);
}
- if (selectName) select.value = selectName;
- updateVoiceHint();
- updateGenerateEnabled();
}
-function updateVoiceHint() {
- const voice = state.voices.find((v) => v.name === $("voice").value);
+function renderVoiceCard() {
+ const card = $("voice-card");
const hint = $("voice-hint");
- if (!voice) {
- hint.textContent = "no voices yet — enroll one from a few recordings";
+ card.textContent = "";
+
+ if (!state.voice) {
+ card.append(el("div", { class: "voice-name", text: "No voice yet" }));
+ hint.textContent =
+ "Enrol a voice below from a few of your own recordings, then close this sheet.";
return;
}
- hint.textContent = voice.has_holdout
- ? "verifies against a held-out clip the model never conditions on"
- : "no holdout clip — verification compares against the reference itself";
+
+ const name = el("div", { class: "voice-name", text: state.voice.name });
+ const meta = el("div", {
+ class: "voice-meta numeric",
+ text: `${formatDuration(state.voice.duration_seconds)} reference`,
+ });
+ card.append(name, meta);
+
+ hint.textContent = state.voice.has_holdout
+ ? "Takes are checked against a held-out clip the model never conditions on."
+ : "No held-out clip, so takes are checked against the reference itself.";
}
-/* ---------- doctor ---------- */
+function renderIdle() {
+ const idle = $("idle-state");
+ idle.textContent = "";
+ if (!state.voice) {
+ idle.append(
+ document.createTextNode("No voice is enrolled yet. "),
+ el("button", { class: "link", type: "button", text: "Add one in Settings", id: "idle-settings" }),
+ document.createTextNode("."),
+ );
+ $("idle-settings").addEventListener("click", openSettings);
+ return;
+ }
+ idle.textContent =
+ "The first take loads the model, which takes about ten seconds. After that it " +
+ "synthesizes at roughly a fifth of realtime, a chunk at a time.";
+}
-async function refreshDoctor() {
+/* ---------- environment ---------- */
+
+async function refreshEnvironment() {
+ let doctor;
try {
- const doctor = await fetchJson("/api/doctor");
- const device = doctor.device ? doctor.device.toUpperCase() : "no torch";
- const weights = doctor.weights_cached ? "weights cached" : "first run downloads weights";
- $("env").innerHTML = `${device} · ${weights}`;
- $("version").textContent = `double-chin ${doctor.version}`;
+ doctor = await api.doctor();
} catch (_) {
- $("env").textContent = "";
+ $("engine-pill").textContent = "";
+ return;
+ }
+
+ const device = doctor.device ? doctor.device.toUpperCase() : "no torch";
+ $("engine-pill").textContent = "";
+ $("engine-pill").append(
+ el("b", { text: device }),
+ document.createTextNode(doctor.weights_cached ? " · ready" : " · first run downloads weights"),
+ );
+ $("version").textContent = `v${doctor.version}`;
+
+ const facts = $("env-facts");
+ facts.textContent = "";
+ const rows = [
+ ["Device", device],
+ ["PyTorch", doctor.torch ?? "not installed"],
+ ["Model weights", doctor.weights_cached ? "cached" : "download on first take"],
+ ["Voices", String(doctor.voices)],
+ ["Home", doctor.double_chin_home],
+ ];
+ for (const [term, value] of rows) {
+ facts.append(el("dt", { text: term }), el("dd", { text: value }));
}
}
-/* ---------- script stats ---------- */
+/* ---------- script ---------- */
function updateScriptStats() {
const text = $("script").value;
const chars = text.length;
- $("script-stats").textContent = `${chars.toLocaleString()} chars`;
- if (chars > 0) {
- // Rough planning figures: ~15 chars/s spoken, ~0.2x realtime synthesis.
- const audioSeconds = chars / 15;
- const wallSeconds = audioSeconds / 0.2;
- $("eta").textContent =
- `≈ ${formatSeconds(audioSeconds)} of audio · rough synthesis time ${formatSeconds(wallSeconds)}`;
- } else {
- $("eta").textContent = "";
- }
- updateGenerateEnabled();
-}
-function formatSeconds(total) {
- const seconds = Math.round(total);
- if (seconds < 90) return `${seconds}s`;
- return `${Math.round(seconds / 60)}min`;
+ $("script-stats").textContent = chars === 0
+ ? ""
+ : `${formatCount(chars)} characters · about ${formatDuration(estimateAudioSeconds(text))} of audio`;
+
+ $("eta").textContent = chars === 0
+ ? ""
+ : `Roughly ${formatDuration(estimateWallSeconds(text))} to synthesize`;
+
+ updateGenerateEnabled();
}
function updateGenerateEnabled() {
$("generate").disabled =
- state.jobRunning || !$("voice").value || $("script").value.trim() === "";
+ state.jobRunning || state.voice === null || $("script").value.trim() === "";
+}
+
+/* ---------- delivery ---------- */
+
+function setRangeFill(input) {
+ const min = parseFloat(input.min);
+ const max = parseFloat(input.max);
+ const ratio = (parseFloat(input.value) - min) / (max - min);
+ input.style.setProperty("--fill", `${(ratio * 100).toFixed(2)}%`);
+}
+
+function knobValue(id) {
+ return parseFloat($(id).value);
+}
+
+/** Keeps the range fills, the numeric outputs, and the collapsed summary in step. */
+function syncDelivery() {
+ const changed = [];
+ for (const [id, fallback] of Object.entries(DELIVERY_DEFAULTS)) {
+ const input = $(id);
+ setRangeFill(input);
+ const value = knobValue(id);
+ const suffix = id === "rate" ? "×" : "";
+ $(`${id}-val`).textContent = `${value.toFixed(2)}${suffix}`;
+ if (Math.abs(value - fallback) > 1e-9) {
+ changed.push(`${KNOB_LABELS[id]} ${value.toFixed(2)}${suffix}`);
+ }
+ }
+ if ($("seed").value.trim() !== "") changed.push("fixed seed");
+
+ $("delivery-summary").textContent = changed.length === 0 ? "Default" : changed.join(" · ");
+}
+
+function resetDelivery() {
+ for (const [id, fallback] of Object.entries(DELIVERY_DEFAULTS)) {
+ $(id).value = String(fallback);
+ }
+ $("seed").value = "";
+ syncDelivery();
}
/* ---------- generate ---------- */
async function generate() {
+ if ($("generate").disabled) return;
+
const seedRaw = $("seed").value.trim();
const payload = {
- voice: $("voice").value,
+ voice: state.voice.name,
text: $("script").value,
- exaggeration: parseFloat($("exaggeration").value),
- cfg_weight: parseFloat($("cfg").value),
- temperature: parseFloat($("temperature").value),
- rate: parseFloat($("rate").value),
+ exaggeration: knobValue("exaggeration"),
+ cfg_weight: knobValue("cfg"),
+ temperature: knobValue("temperature"),
+ rate: knobValue("rate"),
seed: seedRaw === "" ? null : parseInt(seedRaw, 10),
};
let job;
try {
- job = await fetchJson("/api/jobs", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(payload),
- });
+ job = await api.createJob(payload);
} catch (error) {
+ resetJobCard();
+ setStatus("Failed", "error");
showJobError(error.message);
return;
}
@@ -133,80 +267,89 @@ async function generate() {
followJob(job.job_id);
}
-function beginJobUi() {
- state.jobRunning = true;
- state.jobStartedAt = Date.now();
- updateGenerateEnabled();
+/** Clears the job card of any previous run and brings it forward. */
+function resetJobCard() {
$("idle-state").hidden = true;
$("result").hidden = true;
$("job-state").hidden = false;
$("job-error").hidden = true;
- $("chunk-list").innerHTML = "";
+ $("chunk-list").textContent = "";
+ $("job-timer").textContent = "";
+ $("bar-fill").style.width = "0%";
+}
+
+function beginJobUi() {
+ state.jobRunning = true;
+ state.jobStartedAt = Date.now();
+ updateGenerateEnabled();
+
+ resetJobCard();
$("bar-fill").style.width = "2%";
- setStatus("model loading…", "");
+ setStatus("Loading the model…", "");
+
state.timerHandle = setInterval(() => {
- const elapsed = Math.round((Date.now() - state.jobStartedAt) / 1000);
- $("job-timer").textContent = `${elapsed}s`;
+ $("job-timer").textContent = formatClock((Date.now() - state.jobStartedAt) / 1000);
}, 1000);
}
-function setStatus(text, cls) {
+function setStatus(text, tone) {
const status = $("job-status");
status.textContent = text;
- status.className = `status ${cls}`;
+ status.className = `status ${tone}`;
}
function followJob(jobId) {
- const source = new EventSource(`/api/jobs/${jobId}/events`);
+ const source = api.jobEvents(jobId);
state.eventSource = source;
source.addEventListener("progress", (event) => {
const data = JSON.parse(event.data);
- setStatus(`synthesizing chunk ${data.chunk}/${data.total}`, "");
+ setStatus(`Synthesizing ${data.chunk} of ${data.total}`, "");
renderChunk(data);
- $("bar-fill").style.width = `${((data.chunk - 1) / data.total) * 100}%`;
+ $("bar-fill").style.width = `${((data.chunk - 0.5) / data.total) * 100}%`;
});
source.addEventListener("verifying", (event) => {
const data = JSON.parse(event.data);
markAllChunksDone();
$("bar-fill").style.width = "96%";
- setStatus(`verifying speaker similarity (vs ${data.against})…`, "");
+ setStatus(`Checking the voice against ${data.against}…`, "");
});
source.addEventListener("done", (event) => {
const record = JSON.parse(event.data);
finishJob();
- setStatus("done", "done");
+ setStatus("Done", "done");
$("bar-fill").style.width = "100%";
showResult(record);
refreshHistory();
});
source.addEventListener("error", (event) => {
- // Fired both for server-sent error events (with data) and transport
- // errors (without); a closed stream after "done" also lands here.
+ // Fires both for server-sent error events (which carry data) and for
+ // transport errors (which do not); a stream closed after "done" also
+ // lands here, hence the jobRunning guard.
if (event.data) {
const data = JSON.parse(event.data);
finishJob();
- setStatus("failed", "error");
+ setStatus("Failed", "error");
showJobError(data.message);
} else if (state.jobRunning) {
finishJob();
- setStatus("connection lost", "error");
- showJobError("Lost the progress stream. The job may still be running — check History shortly.");
+ setStatus("Lost the connection", "error");
+ showJobError(
+ "The progress stream dropped. The take may still be running — check Recent in a moment.",
+ );
}
});
}
function renderChunk(data) {
- const list = $("chunk-list");
markAllChunksDone();
- const item = document.createElement("li");
- item.className = "active";
- item.innerHTML = `${data.chunk}/${data.total}`;
- item.appendChild(document.createTextNode(data.text));
- list.appendChild(item);
+ const item = el("li", { class: "active" });
+ item.append(el("span", { class: "idx", text: `${data.chunk}/${data.total}` }));
+ item.append(document.createTextNode(data.text));
+ $("chunk-list").append(item);
item.scrollIntoView({ block: "nearest" });
}
@@ -233,104 +376,121 @@ function showJobError(message) {
/* ---------- result ---------- */
-function verdictClass(verdict) {
- if (!verdict) return "plain";
- if (verdict.includes("strong")) return "ok";
- if (verdict === "match") return "ok";
- if (verdict === "borderline") return "warn";
- return "err";
-}
-
function showResult(record) {
+ $("idle-state").hidden = true;
+ $("job-state").hidden = true;
$("result").hidden = false;
+
const chip = $("verdict-chip");
if (record.similarity != null) {
- chip.textContent = `${record.similarity.toFixed(3)} · ${record.verdict} (vs ${record.compared_against})`;
- chip.className = `chip ${verdictClass(record.verdict)}`;
+ chip.textContent = `${record.verdict} · ${record.similarity.toFixed(3)}`;
+ chip.className = `verdict ${verdictTone(record.verdict)}`;
} else {
- chip.textContent = "not verified";
- chip.className = "chip plain";
+ chip.textContent = "Not verified";
+ chip.className = "verdict plain";
}
+
$("result-meta").textContent =
- `${record.audio_seconds}s audio · ${record.chunk_count} chunks · ` +
- `${record.wall_seconds}s on ${record.device}`;
+ `${formatDuration(record.audio_seconds)} of audio · ${pluralize(record.chunk_count, "chunk")} · ` +
+ `${formatDuration(record.wall_seconds)} on ${record.device}`;
+
const player = $("player");
- player.src = `/api/audio/${record.id}`;
- $("download").href = `/api/audio/${record.id}?download=true`;
- player.play().catch(() => { /* autoplay may be blocked; the controls remain */ });
+ player.src = api.audioUrl(record.id);
+ $("download").href = api.audioUrl(record.id, true);
+ player.play().catch(() => { /* autoplay may be blocked; the controls still work */ });
}
/* ---------- history ---------- */
async function refreshHistory() {
- const records = await fetchJson("/api/history");
+ let records;
+ try {
+ records = await api.listHistory();
+ } catch (_) {
+ return;
+ }
+
const container = $("history");
- container.innerHTML = "";
+ container.textContent = "";
+
if (records.length === 0) {
- container.innerHTML = '
nothing generated yet
';
+ container.append(el("p", { class: "history-empty", text: "Nothing generated yet." }));
return;
}
+
for (const record of records) {
- const item = document.createElement("div");
- item.className = "item";
- item.setAttribute("role", "button");
- item.tabIndex = 0;
-
- const text = document.createElement("div");
- text.className = "txt";
- text.textContent = record.text_preview;
-
- const score = document.createElement("div");
- score.className = "score";
- if (record.similarity != null) {
- score.textContent = record.similarity.toFixed(3);
- score.classList.add(verdictClass(record.verdict));
- }
+ container.append(historyRow(record));
+ }
+}
- const sub = document.createElement("div");
- sub.className = "sub";
- const when = new Date(record.created).toLocaleString();
- sub.textContent =
- `${record.voice} · ${record.audio_seconds}s · ${when}` +
- (record.audio_available ? "" : " · audio missing");
-
- item.append(text, score, sub);
- if (record.audio_available) {
- const play = () => {
- $("result").hidden = false;
- showResult(record);
- };
- item.addEventListener("click", play);
- item.addEventListener("keydown", (event) => {
- if (event.key === "Enter" || event.key === " ") { event.preventDefault(); play(); }
- });
- }
- container.appendChild(item);
+function historyRow(record) {
+ const playable = record.audio_available;
+ const item = el("div", {
+ class: "history-item",
+ role: "button",
+ tabindex: playable ? "0" : "-1",
+ });
+ if (!playable) item.setAttribute("aria-disabled", "true");
+
+ item.append(el("div", { class: "txt", text: record.text_preview }));
+
+ const score = el("div", { class: "score" });
+ if (record.similarity != null) {
+ score.textContent = record.similarity.toFixed(3);
+ score.classList.add(verdictTone(record.verdict));
}
+ item.append(score);
+
+ const parts = [record.voice, formatDuration(record.audio_seconds), formatWhen(record.created)];
+ if (!playable) parts.push("audio missing");
+ item.append(el("div", { class: "sub", text: parts.filter(Boolean).join(" · ") }));
+
+ if (playable) {
+ const play = () => showResult(record);
+ item.addEventListener("click", play);
+ item.addEventListener("keydown", (event) => {
+ if (event.key === "Enter" || event.key === " ") {
+ event.preventDefault();
+ play();
+ }
+ });
+ }
+ return item;
}
-/* ---------- enroll ---------- */
+/* ---------- settings ---------- */
+
+function openSettings() {
+ const sheet = $("settings");
+ if (!sheet.open) sheet.showModal();
+}
+
+function closeSettings() {
+ const sheet = $("settings");
+ if (sheet.open) sheet.close();
+}
async function enroll(event) {
event.preventDefault();
- const name = $("enroll-name").value.trim();
- const files = $("enroll-files").files;
const status = $("enroll-status");
const formData = new FormData();
- formData.append("name", name);
- for (const file of files) formData.append("files", file);
+ formData.append("name", $("enroll-name").value.trim());
+ formData.append("overwrite", $("enroll-overwrite").checked ? "true" : "false");
+ for (const file of $("enroll-files").files) formData.append("files", file);
$("enroll-btn").disabled = true;
- status.textContent = "processing recordings…";
+ status.textContent = "Processing the recordings…";
try {
- const info = await fetchJson("/api/voices", { method: "POST", body: formData });
- status.textContent =
- `enrolled '${info.name}' (${info.duration_seconds}s reference` +
- (info.has_holdout ? ", holdout reserved)" : ")");
- await refreshVoices(info.name);
+ const info = await api.enrollVoice(formData);
+ status.textContent = info.has_holdout
+ ? `Enrolled ${info.name} with a held-out clip reserved.`
+ : `Enrolled ${info.name}.`;
$("enroll-form").reset();
+ await Promise.all([refreshVoice(info.name), refreshEnvironment()]);
} catch (error) {
- status.textContent = `error: ${error.message}`;
+ status.textContent = error.status === 409
+ ? `${error.message} Tick the replace box to overwrite it.`
+ : error.message;
} finally {
$("enroll-btn").disabled = false;
}
@@ -341,28 +501,53 @@ async function enroll(event) {
function wireControls() {
$("generate").addEventListener("click", generate);
$("script").addEventListener("input", updateScriptStats);
- $("voice").addEventListener("change", updateVoiceHint);
- $("enroll-toggle").addEventListener("click", () => {
- const form = $("enroll-form");
- form.hidden = !form.hidden;
- $("enroll-toggle").setAttribute("aria-expanded", String(!form.hidden));
- });
- $("enroll-form").addEventListener("submit", enroll);
+ for (const id of Object.keys(DELIVERY_DEFAULTS)) {
+ $(id).addEventListener("input", syncDelivery);
+ }
+ $("seed").addEventListener("input", syncDelivery);
+ $("reset-delivery").addEventListener("click", resetDelivery);
$("script-file").addEventListener("change", async () => {
const file = $("script-file").files[0];
- if (file) {
- $("script").value = await file.text();
- updateScriptStats();
- }
+ if (!file) return;
+ $("script").value = await file.text();
+ $("script-file").value = "";
+ updateScriptStats();
});
- for (const id of ["exaggeration", "cfg", "temperature", "rate"]) {
- $(id).addEventListener("input", () => {
- $(`${id}-val`).textContent = parseFloat($(id).value).toFixed(2);
- });
+ $("voice-select").addEventListener("change", (event) => {
+ storeVoice(event.target.value);
+ refreshVoice(event.target.value);
+ });
+
+ $("settings-open").addEventListener("click", openSettings);
+ $("settings-close").addEventListener("click", closeSettings);
+ $("enroll-form").addEventListener("submit", enroll);
+
+ // Clicking the dimmed area outside the sheet dismisses it, as a sheet should.
+ $("settings").addEventListener("click", (event) => {
+ if (event.target === $("settings")) closeSettings();
+ });
+
+ document.addEventListener("keydown", (event) => {
+ if (!(event.metaKey || event.ctrlKey) || event.key !== "Enter") return;
+ if ($("settings").open) return;
+ event.preventDefault();
+ generate();
+ });
+}
+
+/* ---------- tiny DOM helper ---------- */
+
+function el(tag, options = {}) {
+ const node = document.createElement(tag);
+ const { text, ...attributes } = options;
+ for (const [name, value] of Object.entries(attributes)) {
+ node.setAttribute(name, value);
}
+ if (text !== undefined) node.textContent = text;
+ return node;
}
init();
diff --git a/src/double_chin/studio/static/fonts/JetBrainsMono-Medium.ttf b/src/double_chin/studio/static/fonts/JetBrainsMono-Medium.ttf
deleted file mode 100644
index dc2e5d08677d603b6755dc6c34fc8ad9aa6fc8a5..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 270316
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
z
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