bep40 commited on
Commit
cd38f6c
·
verified ·
1 Parent(s): 33ab50b

Upload src/app.js

Browse files
Files changed (1) hide show
  1. src/app.js +15 -6
src/app.js CHANGED
@@ -8,6 +8,9 @@
8
  * silently failing, we wait for the user to click "Start talking" before
9
  * any WebSocket connection — that click resumes AudioContext, audio plays,
10
  * and HeadAudio lip-sync works.
 
 
 
11
  */
12
 
13
  import { S2sWsRealtimeClient } from "./s2s/s2s-ws-client.js";
@@ -20,6 +23,10 @@ const VOICES = [
20
  ];
21
  const DEFAULT_VOICE = "Sohee";
22
 
 
 
 
 
23
  // ── Greeting ─────────────────────────────────────────────────────────────
24
  /**
25
  * Fetch a SHORT hot news headline to weave into the greeting.
@@ -188,7 +195,7 @@ function makeDraggable() {
188
  let dragging = false; let startX, startY, startLeft, startTop;
189
  function getPos(e) { const p = e.changedTouches ? e.changedTouches[0] : e; return { x: p.clientX, y: p.clientY }; }
190
  function onStart(e) { if (e.target.closest("#chat-header-actions") || e.target.closest("#chat-avatar-select")) return; const p = getPos(e); dragging = true; const rect = textChat.getBoundingClientRect(); startX = p.x; startY = p.y; startLeft = rect.left; startTop = rect.top; textChat.classList.add("dragging"); e.preventDefault(); }
191
- function onMove(e) { if (!dragging) return; const p = getPos(e); textChat.style.left = `${startLeft + p.x - startX}px`; textChat.style.top = `${startTop + p.y - startY}px`; textChat.style.right = "auto"; textChat.style.bottom = "auto"; textChat.style.width = ""; textChat.style.height = ""; e.preventDefault(); }
192
  function onEnd() { if (!dragging) return; dragging = false; textChat.classList.remove("dragging"); clampRect(); }
193
  chatHeader.addEventListener("mousedown", onStart); document.addEventListener("mousemove", onMove); document.addEventListener("mouseup", onEnd);
194
  chatHeader.addEventListener("touchstart", onStart, { passive: false }); document.addEventListener("touchmove", onMove, { passive: false }); document.addEventListener("touchend", onEnd);
@@ -220,7 +227,6 @@ function sendTextMessage() {
220
  if (!text) return;
221
  addChatMessage("user", text); chatInput.value = "";
222
  if (!client || !sessionInProgress) {
223
- const url = new URL(location.href); url.searchParams.set("fakemic", "1"); history.replaceState(null, "", url.href);
224
  void startTextSession(text); return;
225
  }
226
  if (!sendTextViaSession(text)) setCaption("QUEUED…");
@@ -305,7 +311,7 @@ async function startVoiceSession() {
305
  // Await AudioContext resume — this runs in user-gesture handler so it resolves
306
  await stage.resume();
307
  let micStream;
308
- if (new URLSearchParams(location.search).has("fakemic")) { const ctx = /** @type {AudioContext} */ (stage.audioCtx); micStream = ctx.createMediaStreamDestination().stream; }
309
  else { try { micStream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } }); } catch { setCaption("MIC BLOCKED", "error"); sessionInProgress = false; return; } }
310
  const audioCtx = stage.audioCtx; const voiceSink = stage.voiceSink;
311
  if (!audioCtx || !voiceSink) { sessionInProgress = false; return; }
@@ -375,7 +381,7 @@ mainBtn.addEventListener("click", () => {
375
  if (mainAction === "start") {
376
  // User gesture! AudioContext.resume() will resolve → audio plays + lipsync works.
377
  if (sessionInProgress) return;
378
- if (new URLSearchParams(location.search).has("fakemic")) {
379
  void startTextSession();
380
  } else {
381
  void startVoiceSession();
@@ -405,7 +411,6 @@ async function boot() {
405
  directUrlRow.hidden = !config.allowDirect;
406
  await fetchAvatarList(); populateAvatarSelects(settings.avatar);
407
  makeDraggable(); makeResizable();
408
- const url = new URL(location.href); url.searchParams.set("fakemic", "1"); history.replaceState(null, "", url.href);
409
  setCaption("WAKING HER UP…"); setMainButton("busy", "Loading…");
410
  // Pre-fetch news hook in parallel with avatar
411
  const newsPromise = (settings.avatar === "vuong.glb" || !settings.avatar) ? getHotNewsGreeting().catch(() => null) : Promise.resolve(null);
@@ -419,5 +424,9 @@ async function boot() {
419
  // context is silent and HeadAudio won't process any frames.
420
  // User must tap "Start talking" → click provides user gesture →
421
  // AudioContext.resume() resolves → audio plays → HeadAudio drives lipsync.
 
 
 
 
422
  }
423
- void boot();
 
8
  * silently failing, we wait for the user to click "Start talking" before
9
  * any WebSocket connection — that click resumes AudioContext, audio plays,
10
  * and HeadAudio lip-sync works.
11
+ *
12
+ * ?fakemic=1 — opt-in URL query param for testing text-only mode
13
+ * WITHOUT a microphone. Only activates when user explicitly adds it to URL.
14
  */
15
 
16
  import { S2sWsRealtimeClient } from "./s2s/s2s-ws-client.js";
 
23
  ];
24
  const DEFAULT_VOICE = "Sohee";
25
 
26
+ // ── Detect fakemic mode from URL (user opt-in only) ──────────────────────
27
+ const _urlParams = new URLSearchParams(location.search);
28
+ const FAKEMIC_MODE = _urlParams.has("fakemic");
29
+
30
  // ── Greeting ─────────────────────────────────────────────────────────────
31
  /**
32
  * Fetch a SHORT hot news headline to weave into the greeting.
 
195
  let dragging = false; let startX, startY, startLeft, startTop;
196
  function getPos(e) { const p = e.changedTouches ? e.changedTouches[0] : e; return { x: p.clientX, y: p.clientY }; }
197
  function onStart(e) { if (e.target.closest("#chat-header-actions") || e.target.closest("#chat-avatar-select")) return; const p = getPos(e); dragging = true; const rect = textChat.getBoundingClientRect(); startX = p.x; startY = p.y; startLeft = rect.left; startTop = rect.top; textChat.classList.add("dragging"); e.preventDefault(); }
198
+ function onMove(e) { if (!dragging) return; const p = getPos(e); textChat.style.left = `${startLeft + p.x - startX}px`; textChat.style.top = `${startTop + p.y - startY}px`; textChat.style.right = "auto"; textChat.style.bottom = "auto"; e.preventDefault(); }
199
  function onEnd() { if (!dragging) return; dragging = false; textChat.classList.remove("dragging"); clampRect(); }
200
  chatHeader.addEventListener("mousedown", onStart); document.addEventListener("mousemove", onMove); document.addEventListener("mouseup", onEnd);
201
  chatHeader.addEventListener("touchstart", onStart, { passive: false }); document.addEventListener("touchmove", onMove, { passive: false }); document.addEventListener("touchend", onEnd);
 
227
  if (!text) return;
228
  addChatMessage("user", text); chatInput.value = "";
229
  if (!client || !sessionInProgress) {
 
230
  void startTextSession(text); return;
231
  }
232
  if (!sendTextViaSession(text)) setCaption("QUEUED…");
 
311
  // Await AudioContext resume — this runs in user-gesture handler so it resolves
312
  await stage.resume();
313
  let micStream;
314
+ if (FAKEMIC_MODE) { const ctx = /** @type {AudioContext} */ (stage.audioCtx); micStream = ctx.createMediaStreamDestination().stream; }
315
  else { try { micStream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } }); } catch { setCaption("MIC BLOCKED", "error"); sessionInProgress = false; return; } }
316
  const audioCtx = stage.audioCtx; const voiceSink = stage.voiceSink;
317
  if (!audioCtx || !voiceSink) { sessionInProgress = false; return; }
 
381
  if (mainAction === "start") {
382
  // User gesture! AudioContext.resume() will resolve → audio plays + lipsync works.
383
  if (sessionInProgress) return;
384
+ if (FAKEMIC_MODE) {
385
  void startTextSession();
386
  } else {
387
  void startVoiceSession();
 
411
  directUrlRow.hidden = !config.allowDirect;
412
  await fetchAvatarList(); populateAvatarSelects(settings.avatar);
413
  makeDraggable(); makeResizable();
 
414
  setCaption("WAKING HER UP…"); setMainButton("busy", "Loading…");
415
  // Pre-fetch news hook in parallel with avatar
416
  const newsPromise = (settings.avatar === "vuong.glb" || !settings.avatar) ? getHotNewsGreeting().catch(() => null) : Promise.resolve(null);
 
424
  // context is silent and HeadAudio won't process any frames.
425
  // User must tap "Start talking" → click provides user gesture →
426
  // AudioContext.resume() resolves → audio plays → HeadAudio drives lipsync.
427
+ //
428
+ // ?fakemic=1 is an OPT-IN testing flag. Setting it automatically would
429
+ // break the direct domain (bep40-gemma-avatar.hf.space) by routing to
430
+ // text-only mode without a working backend.
431
  }
432
+ void boot();