Spaces:
Running
Running
Add subtitles toggle in settings
Browse files- index.html +4 -0
- src/app.js +9 -0
- src/style.css +13 -0
index.html
CHANGED
|
@@ -61,6 +61,10 @@
|
|
| 61 |
Voice
|
| 62 |
<select id="voice"></select>
|
| 63 |
</label>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
<label>
|
| 65 |
Extra instructions
|
| 66 |
<textarea
|
|
|
|
| 61 |
Voice
|
| 62 |
<select id="voice"></select>
|
| 63 |
</label>
|
| 64 |
+
<label class="check-row">
|
| 65 |
+
<input type="checkbox" id="subtitles-toggle" />
|
| 66 |
+
Show subtitles while she speaks
|
| 67 |
+
</label>
|
| 68 |
<label>
|
| 69 |
Extra instructions
|
| 70 |
<textarea
|
src/app.js
CHANGED
|
@@ -39,6 +39,7 @@ const STORAGE_KEYS = {
|
|
| 39 |
voice: "avatar.voice",
|
| 40 |
instructions: "avatar.instructions",
|
| 41 |
directUrl: "avatar.directUrl",
|
|
|
|
| 42 |
};
|
| 43 |
|
| 44 |
/** Function tools declared to the backend: the model plays the avatar. */
|
|
@@ -95,6 +96,7 @@ const settingsDialog = /** @type {HTMLDialogElement} */ ($("#settings"));
|
|
| 95 |
const inputVoice = /** @type {HTMLSelectElement} */ ($("#voice"));
|
| 96 |
const inputInstructions = /** @type {HTMLTextAreaElement} */ ($("#instructions"));
|
| 97 |
const inputDirectUrl = /** @type {HTMLInputElement} */ ($("#direct-url"));
|
|
|
|
| 98 |
const directUrlRow = $("#direct-url-row");
|
| 99 |
|
| 100 |
// ββ State ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -111,6 +113,8 @@ function loadSettings() {
|
|
| 111 |
voice: localStorage.getItem(STORAGE_KEYS.voice) || DEFAULT_VOICE,
|
| 112 |
instructions: localStorage.getItem(STORAGE_KEYS.instructions) || "",
|
| 113 |
directUrl: localStorage.getItem(STORAGE_KEYS.directUrl) || "",
|
|
|
|
|
|
|
| 114 |
};
|
| 115 |
}
|
| 116 |
let settings = loadSettings();
|
|
@@ -119,6 +123,7 @@ function saveSettings() {
|
|
| 119 |
localStorage.setItem(STORAGE_KEYS.voice, settings.voice);
|
| 120 |
localStorage.setItem(STORAGE_KEYS.instructions, settings.instructions);
|
| 121 |
localStorage.setItem(STORAGE_KEYS.directUrl, settings.directUrl);
|
|
|
|
| 122 |
}
|
| 123 |
|
| 124 |
/** Persona + whatever extra guidance the user typed in Settings. */
|
|
@@ -136,6 +141,7 @@ function setCaption(text, kind = "") {
|
|
| 136 |
|
| 137 |
/** @param {string} text */
|
| 138 |
function showSubtitles(text) {
|
|
|
|
| 139 |
clearTimeout(subtitleTimer);
|
| 140 |
subtitles.textContent = text;
|
| 141 |
subtitles.classList.add("visible");
|
|
@@ -347,6 +353,7 @@ settingsBtn.addEventListener("click", () => {
|
|
| 347 |
inputVoice.value = settings.voice;
|
| 348 |
inputInstructions.value = settings.instructions;
|
| 349 |
inputDirectUrl.value = settings.directUrl;
|
|
|
|
| 350 |
settingsDialog.showModal();
|
| 351 |
});
|
| 352 |
|
|
@@ -355,8 +362,10 @@ settingsDialog.addEventListener("close", () => {
|
|
| 355 |
voice: inputVoice.value || DEFAULT_VOICE,
|
| 356 |
instructions: inputInstructions.value,
|
| 357 |
directUrl: inputDirectUrl.value.trim(),
|
|
|
|
| 358 |
};
|
| 359 |
saveSettings();
|
|
|
|
| 360 |
// Voice/instructions apply live to an ongoing session.
|
| 361 |
client?.updateSession({ voice: settings.voice, instructions: effectiveInstructions() });
|
| 362 |
});
|
|
|
|
| 39 |
voice: "avatar.voice",
|
| 40 |
instructions: "avatar.instructions",
|
| 41 |
directUrl: "avatar.directUrl",
|
| 42 |
+
subtitles: "avatar.subtitles",
|
| 43 |
};
|
| 44 |
|
| 45 |
/** Function tools declared to the backend: the model plays the avatar. */
|
|
|
|
| 96 |
const inputVoice = /** @type {HTMLSelectElement} */ ($("#voice"));
|
| 97 |
const inputInstructions = /** @type {HTMLTextAreaElement} */ ($("#instructions"));
|
| 98 |
const inputDirectUrl = /** @type {HTMLInputElement} */ ($("#direct-url"));
|
| 99 |
+
const inputSubtitles = /** @type {HTMLInputElement} */ ($("#subtitles-toggle"));
|
| 100 |
const directUrlRow = $("#direct-url-row");
|
| 101 |
|
| 102 |
// ββ State ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 113 |
voice: localStorage.getItem(STORAGE_KEYS.voice) || DEFAULT_VOICE,
|
| 114 |
instructions: localStorage.getItem(STORAGE_KEYS.instructions) || "",
|
| 115 |
directUrl: localStorage.getItem(STORAGE_KEYS.directUrl) || "",
|
| 116 |
+
// Off by default: the face already carries the conversation.
|
| 117 |
+
subtitles: localStorage.getItem(STORAGE_KEYS.subtitles) === "1",
|
| 118 |
};
|
| 119 |
}
|
| 120 |
let settings = loadSettings();
|
|
|
|
| 123 |
localStorage.setItem(STORAGE_KEYS.voice, settings.voice);
|
| 124 |
localStorage.setItem(STORAGE_KEYS.instructions, settings.instructions);
|
| 125 |
localStorage.setItem(STORAGE_KEYS.directUrl, settings.directUrl);
|
| 126 |
+
localStorage.setItem(STORAGE_KEYS.subtitles, settings.subtitles ? "1" : "0");
|
| 127 |
}
|
| 128 |
|
| 129 |
/** Persona + whatever extra guidance the user typed in Settings. */
|
|
|
|
| 141 |
|
| 142 |
/** @param {string} text */
|
| 143 |
function showSubtitles(text) {
|
| 144 |
+
if (!settings.subtitles) return;
|
| 145 |
clearTimeout(subtitleTimer);
|
| 146 |
subtitles.textContent = text;
|
| 147 |
subtitles.classList.add("visible");
|
|
|
|
| 353 |
inputVoice.value = settings.voice;
|
| 354 |
inputInstructions.value = settings.instructions;
|
| 355 |
inputDirectUrl.value = settings.directUrl;
|
| 356 |
+
inputSubtitles.checked = settings.subtitles;
|
| 357 |
settingsDialog.showModal();
|
| 358 |
});
|
| 359 |
|
|
|
|
| 362 |
voice: inputVoice.value || DEFAULT_VOICE,
|
| 363 |
instructions: inputInstructions.value,
|
| 364 |
directUrl: inputDirectUrl.value.trim(),
|
| 365 |
+
subtitles: inputSubtitles.checked,
|
| 366 |
};
|
| 367 |
saveSettings();
|
| 368 |
+
if (!settings.subtitles) subtitles.classList.remove("visible");
|
| 369 |
// Voice/instructions apply live to an ongoing session.
|
| 370 |
client?.updateSession({ voice: settings.voice, instructions: effectiveInstructions() });
|
| 371 |
});
|
src/style.css
CHANGED
|
@@ -302,6 +302,19 @@ body {
|
|
| 302 |
resize: vertical;
|
| 303 |
}
|
| 304 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
.dialog-actions {
|
| 306 |
display: flex;
|
| 307 |
justify-content: flex-end;
|
|
|
|
| 302 |
resize: vertical;
|
| 303 |
}
|
| 304 |
|
| 305 |
+
#settings .check-row {
|
| 306 |
+
display: flex;
|
| 307 |
+
align-items: center;
|
| 308 |
+
gap: 9px;
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
#settings .check-row input {
|
| 312 |
+
display: inline-block;
|
| 313 |
+
width: auto;
|
| 314 |
+
margin: 0;
|
| 315 |
+
accent-color: var(--text);
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
.dialog-actions {
|
| 319 |
display: flex;
|
| 320 |
justify-content: flex-end;
|