fivetech commited on
Commit
76d53c7
·
1 Parent(s): 1a8cd24

Entrada por texto (teclado ademas de voz) y escote cirurgico con parche de piel en runtime

Browse files
Files changed (4) hide show
  1. chunk-4t726cmy.css +2 -1
  2. chunk-p1yb2ca4.js +248 -2
  3. index.html +9 -0
  4. workers/model-worker.js +8 -3
chunk-4t726cmy.css CHANGED
@@ -1 +1,2 @@
1
- :root{--bg:#050506;--bg-elev:#13151c;--bg-elev-2:#1b1e29;--border:#ffffff14;--border-strong:#ffffff29;--text:#f5f6fa;--text-dim:#f5f6faa6;--text-faint:#f5f6fa6b;--error:#ff6a75;--live:#22d3ee;--font-mono:"Geist Mono",ui-monospace,SFMono-Regular,Menlo,monospace;--radius-sm:8px;--radius-md:14px}*{box-sizing:border-box}html,body{height:100%;margin:0}body{background:var(--bg);color:var(--text);overflow:hidden;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif}#app{position:relative;height:100dvh}#stage{position:absolute;background:#050506;inset:0}#stage canvas{display:block}[hidden]{display:none!important}#loading{position:absolute;display:grid;font-family:var(--font-mono);letter-spacing:.12em;text-transform:uppercase;color:var(--text-faint);background:var(--bg);pointer-events:none;place-items: center;transition:opacity .6s;font-size:12px;font-weight:500;inset:0}#loading.done{opacity:0}#topbar{position:absolute;display:flex;pointer-events:none;justify-content:space-between;align-items: flex-start;padding:18px 20px;top:0;left:0;right:0}#topbar>*{pointer-events:auto}#identity h1{letter-spacing:.01em;margin:0;font-size:15px;font-weight:600}#identity p{color:var(--text-faint);max-width:340px;margin:4px 0 0;font-size:12px;line-height:1.45}#identity strong{color:var(--text-dim);font-weight:500}.icon-btn{display:grid;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-elev);color:var(--text-dim);cursor:pointer;touch-action:manipulation;place-items: center;width:36px;height:36px;padding:0;transition:color .15s,border-color .15s}.icon-btn:hover{color:var(--text);border-color:var(--border-strong)}.icon-btn.active{color:var(--error);border-color:var(--border-strong)}#main-btn{background:var(--text);color:var(--bg);cursor:pointer;touch-action:manipulation;border:none;border-radius:23px;min-width:190px;height:46px;padding:0 26px;transition:transform .12s,opacity .15s;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:14px;font-weight:600}#main-btn:hover:not(:disabled){transform:translateY(-1px)}#main-btn:disabled{pointer-events:none;opacity:.45;cursor:default}#main-btn.live{background:var(--bg-elev);color:var(--text);border:1px solid var(--border-strong)}#controls{position:absolute;display:flex;padding:0 20px calc(22px + env(safe-area-inset-bottom));flex-direction:column;align-items: center;gap:14px;bottom:0;left:0;right:0}#buttons{display:flex;align-items: center;gap:10px}#caption{font-family:var(--font-mono);letter-spacing:.14em;text-transform:uppercase;color:var(--text-faint);transition:color .2s;font-size:11px;font-weight:500}#caption.live{color:var(--live)}#caption.error{color:var(--error);text-align:center;max-width:560px;line-height:1.5}#progress{background:var(--border-strong);overflow:hidden;border-radius:1px;width:min(240px,60vw);height:2px}#progress-fill{background:var(--text-dim);width:0;height:100%;transition:width .3s}#subtitles{position:absolute;border-radius:var(--radius-md);backdrop-filter:blur(6px);border:1px solid var(--border);text-align:center;text-wrap:balance;color:var(--text);opacity:0;pointer-events:none;display:-webkit-box;-webkit-line-clamp:3;overflow:hidden;background:#0a0b10b8;-webkit-box-orient:vertical;max-width:min(640px,100vw - 48px);padding:10px 16px;transition:opacity .25s;font-size:15px;line-height:1.5;bottom:118px;left:50%;transform:translate(-50%)}#subtitles.visible{opacity:1}#settings{border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--bg-elev);color:var(--text);width:min(440px,100vw - 40px);padding:22px;box-shadow:0 4px 18px #00000052}#settings::backdrop{backdrop-filter:blur(2px);background:#05060999}#settings h2{margin:0 0 16px;font-size:15px;font-weight:600}#settings label{display:block;color:var(--text-dim);margin-bottom:14px;font-size:12px}#settings select,#settings textarea,#settings input{display:block;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-elev-2);color:var(--text);width:100%;margin-top:6px;padding:9px 11px;font-family:inherit;font-size:13px}#settings select:focus-visible{outline:1px solid var(--text-dim);outline-offset:1px}#settings textarea:focus-visible{outline:1px solid var(--text-dim);outline-offset:1px}#settings input:focus-visible{outline:1px solid var(--text-dim);outline-offset:1px}#settings textarea{resize:vertical}#settings .hint{display:block;margin-top:6px;font-size:11px;line-height:1.4}#settings .check-row{display:flex;align-items: center;gap:9px}#settings .check-row input{display:inline-block;accent-color:var(--text);width:auto;margin:0}.dialog-actions{display:flex;justify-content:space-between;gap:12px;margin-top:6px}#reset-prompt{color:var(--text-dim);cursor:pointer;background:0 0;border:none;padding:9px 0;font-size:13px}#reset-prompt:hover{color:var(--text)}.dialog-actions .primary{border-radius:var(--radius-sm);background:var(--text);color:var(--bg);cursor:pointer;border:none;padding:9px 20px;font-size:13px;font-weight:600}button:focus-visible{outline:2px solid var(--text-dim);outline-offset:2px}@media (prefers-reduced-motion:reduce){*{transition-duration:.01ms!important}}@media (max-width:600px){#identity p{display:none}#subtitles{font-size:14px;bottom:128px}}
 
 
1
+ :root{--bg:#050506;--bg-elev:#13151c;--bg-elev-2:#1b1e29;--border:#ffffff14;--border-strong:#ffffff29;--text:#f5f6fa;--text-dim:#f5f6faa6;--text-faint:#f5f6fa6b;--error:#ff6a75;--live:#22d3ee;--font-mono:"Geist Mono",ui-monospace,SFMono-Regular,Menlo,monospace;--radius-sm:8px;--radius-md:14px}*{box-sizing:border-box}html,body{height:100%;margin:0}body{background:var(--bg);color:var(--text);overflow:hidden;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif}#app{position:relative;height:100dvh}#stage{position:absolute;background:#050506;inset:0}#stage canvas{display:block}[hidden]{display:none!important}#loading{position:absolute;display:grid;font-family:var(--font-mono);letter-spacing:.12em;text-transform:uppercase;color:var(--text-faint);background:var(--bg);pointer-events:none;place-items: center;transition:opacity .6s;font-size:12px;font-weight:500;inset:0}#loading.done{opacity:0}#topbar{position:absolute;display:flex;pointer-events:none;justify-content:space-between;align-items: flex-start;padding:18px 20px;top:0;left:0;right:0}#topbar>*{pointer-events:auto}#identity h1{letter-spacing:.01em;margin:0;font-size:15px;font-weight:600}#identity p{color:var(--text-faint);max-width:340px;margin:4px 0 0;font-size:12px;line-height:1.45}#identity strong{color:var(--text-dim);font-weight:500}.icon-btn{display:grid;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-elev);color:var(--text-dim);cursor:pointer;touch-action:manipulation;place-items: center;width:36px;height:36px;padding:0;transition:color .15s,border-color .15s}.icon-btn:hover{color:var(--text);border-color:var(--border-strong)}.icon-btn.active{color:var(--error);border-color:var(--border-strong)}#main-btn{background:var(--text);color:var(--bg);cursor:pointer;touch-action:manipulation;border:none;border-radius:23px;min-width:190px;height:46px;padding:0 26px;transition:transform .12s,opacity .15s;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:14px;font-weight:600}#main-btn:hover:not(:disabled){transform:translateY(-1px)}#main-btn:disabled{pointer-events:none;opacity:.45;cursor:default}#main-btn.live{background:var(--bg-elev);color:var(--text);border:1px solid var(--border-strong)}#controls{position:absolute;display:flex;padding:0 20px calc(22px + env(safe-area-inset-bottom));flex-direction:column;align-items: center;gap:14px;bottom:0;left:0;right:0}#buttons{display:flex;align-items: center;gap:10px}#caption{font-family:var(--font-mono);letter-spacing:.14em;text-transform:uppercase;color:var(--text-faint);transition:color .2s;font-size:11px;font-weight:500}#caption.live{color:var(--live)}#caption.error{color:var(--error);text-align:center;max-width:560px;line-height:1.5}#progress{background:var(--border-strong);overflow:hidden;border-radius:1px;width:min(240px,60vw);height:2px}#progress-fill{background:var(--text-dim);width:0;height:100%;transition:width .3s}#subtitles{position:absolute;border-radius:var(--radius-md);backdrop-filter:blur(6px);border:1px solid var(--border);text-align:center;text-wrap:balance;color:var(--text);opacity:0;pointer-events:none;display:-webkit-box;-webkit-line-clamp:3;overflow:hidden;background:#0a0b10b8;-webkit-box-orient:vertical;max-width:min(640px,100vw - 48px);padding:10px 16px;transition:opacity .25s;font-size:15px;line-height:1.5;bottom:118px;left:50%;transform:translate(-50%)}#subtitles.visible{opacity:1}#settings{border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--bg-elev);color:var(--text);width:min(440px,100vw - 40px);padding:22px;box-shadow:0 4px 18px #00000052}#settings::backdrop{backdrop-filter:blur(2px);background:#05060999}#settings h2{margin:0 0 16px;font-size:15px;font-weight:600}#settings label{display:block;color:var(--text-dim);margin-bottom:14px;font-size:12px}#settings select,#settings textarea,#settings input{display:block;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-elev-2);color:var(--text);width:100%;margin-top:6px;padding:9px 11px;font-family:inherit;font-size:13px}#settings select:focus-visible{outline:1px solid var(--text-dim);outline-offset:1px}#settings textarea:focus-visible{outline:1px solid var(--text-dim);outline-offset:1px}#settings input:focus-visible{outline:1px solid var(--text-dim);outline-offset:1px}#settings textarea{resize:vertical}#settings .hint{display:block;margin-top:6px;font-size:11px;line-height:1.4}#settings .check-row{display:flex;align-items: center;gap:9px}#settings .check-row input{display:inline-block;accent-color:var(--text);width:auto;margin:0}.dialog-actions{display:flex;justify-content:space-between;gap:12px;margin-top:6px}#reset-prompt{color:var(--text-dim);cursor:pointer;background:0 0;border:none;padding:9px 0;font-size:13px}#reset-prompt:hover{color:var(--text)}.dialog-actions .primary{border-radius:var(--radius-sm);background:var(--text);color:var(--bg);cursor:pointer;border:none;padding:9px 20px;font-size:13px;font-weight:600}button:focus-visible{outline:2px solid var(--text-dim);outline-offset:2px}#type-form{display:flex;gap:8px;align-items:center;margin:0 0 10px;justify-content:center}#type-input{background:var(--bg-elev);border:1px solid var(--border);border-radius:23px;color:var(--text);font-family:inherit;font-size:14px;padding:0 16px;height:46px;width:min(340px,72vw);outline:none;transition:border-color .15s;touch-action:manipulation}#type-input:focus{border-color:var(--border-strong)}#type-input::placeholder{color:var(--text-faint)}#send-btn{display:grid;place-items:center;background:var(--bg-elev);border:1px solid var(--border);border-radius:23px;color:var(--text-dim);cursor:pointer;touch-action:manipulation;width:46px;height:46px;padding:0;transition:color .15s,border-color .15s}#send-btn:hover{color:var(--text);border-color:var(--border-strong)}#send-btn:focus-visible{outline:2px solid var(--text-dim);outline-offset:2px}
2
+ @media (prefers-reduced-motion:reduce){*{transition-duration:.01ms!important}}@media (max-width:600px){#identity p{display:none}#subtitles{font-size:14px;bottom:128px}}
chunk-p1yb2ca4.js CHANGED
@@ -34416,6 +34416,198 @@ function Ji(e, t) {
34416
  if (e[i] !== void 0) n[i] = e[i]?.clone && !e[i].isTexture ? e[i].clone() : e[i];
34417
  return Object.assign(n, t);
34418
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34419
  function vp(e, t = {}) {
34420
  let { studio: n = !0, materials: i = !0, shadows: s = !0 } = t,
34421
  { renderer: r, scene: o, armature: a } = e,
@@ -34621,6 +34813,7 @@ varying float vShellX; uniform vec2 lidBlink;`,
34621
  envMapIntensity: 0.35,
34622
  });
34623
  }
 
34624
  a.traverse((h) => {
34625
  if (!h.isMesh) return;
34626
  for (let u of [h.material.map, h.material.normalMap, h.material.roughnessMap])
@@ -34967,6 +35160,31 @@ class uh extends EventTarget {
34967
  this._setStatus("processing"),
34968
  this.model.respond(t, e));
34969
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34970
  onText(e, t) {
34971
  let n = this.turn;
34972
  if (!n || n.id !== e) return;
@@ -35282,7 +35500,7 @@ function Ap() {
35282
  w1(t);
35283
  break;
35284
  case "ready":
35285
- if (((Es = !0), Rn?.finished(), Gn)) Vp();
35286
  (xr?.configure(t.sampleRate),
35287
  Object.assign(rn, { device: t.device, modelSampleRate: t.sampleRate }),
35288
  (Sh.hidden = !0),
@@ -35404,7 +35622,10 @@ async function T1() {
35404
  l = new uh({
35405
  voice: a,
35406
  model: {
35407
- respond: (c, h) => ts.postMessage({ kind: "respond", id: c, audio: h }, [h.buffer]),
 
 
 
35408
  abort: (c) => ts.postMessage({ kind: "abort", id: c }),
35409
  reset: () => ts.postMessage({ kind: "reset" }),
35410
  },
@@ -35434,6 +35655,9 @@ async function T1() {
35434
  (yt.endOfReply(), Fp());
35435
  }),
35436
  l.addEventListener("interrupted", () => Ts.classList.remove("visible")),
 
 
 
35437
  l.addEventListener("error", (c) => {
35438
  let { error: h } = c.detail;
35439
  if ((console.error("generation failed:", h), h.fatal))
@@ -35444,8 +35668,30 @@ async function T1() {
35444
  no.classList.remove("active"),
35445
  ai("stop", "End conversation"),
35446
  Ia("connected"),
 
35447
  yt.setConversationState("connected"));
35448
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35449
  async function Wp(e = !1) {
35450
  let t = Ht;
35451
  if (((Ht = null), t?.close(), yt.setConversationState("idle"), Ts.classList.remove("visible"), !e)) Jt(Pa.idle);
 
34416
  if (e[i] !== void 0) n[i] = e[i]?.clone && !e[i].isTexture ? e[i].clone() : e[i];
34417
  return Object.assign(n, t);
34418
  }
34419
+ function mq(c) {
34420
+ let i = c.Wolf3D_Outfit_Top;
34421
+ if (!i) return;
34422
+ let h = new URLSearchParams(location.search),
34423
+ y = h.get("neck") ?? "v";
34424
+ if (y === "off") return;
34425
+ if (h.get("collar") === "off" && c.Ava_Collar) c.Ava_Collar.visible = !1;
34426
+ let p = Number(h.get("neckDepth") ?? 0.1),
34427
+ g = Number(h.get("neckW") ?? 0.108),
34428
+ M = Number(h.get("neckTop") ?? 1.5015),
34429
+ F = Number(h.get("neckFront") ?? 0.014),
34430
+ T = y === "u" ? 0 : 1,
34431
+ b = i.geometry.attributes,
34432
+ R = b.position,
34433
+ _ = b.normal,
34434
+ x = b.skinIndex,
34435
+ P = b.skinWeight;
34436
+ if (!R || !_) return;
34437
+ let K = 10,
34438
+ se = (u, v, w2) => {
34439
+ let S = [],
34440
+ px = 0,
34441
+ py = 0,
34442
+ pz = 0,
34443
+ nx = 0,
34444
+ ny = 0,
34445
+ nz = 0,
34446
+ ch = 0;
34447
+ for (let j2 = 0; j2 < R.count; j2++) {
34448
+ let m = {
34449
+ d:
34450
+ (R.array[j2 * 3] - u) ** 2 +
34451
+ (R.array[j2 * 3 + 1] - v) ** 2 +
34452
+ (R.array[j2 * 3 + 2] - w2) ** 2,
34453
+ j: j2,
34454
+ };
34455
+ if (S.length < K) S.push(m);
34456
+ else if (m.d < S[K - 1].d) ((S[K - 1] = m), S.sort((B, D) => B.d - D.d));
34457
+ }
34458
+ for (let m of S) {
34459
+ let j2 = m.j,
34460
+ q = 1 / (m.d + 1e-6);
34461
+ (ch += q),
34462
+ (px += R.array[j2 * 3] * q),
34463
+ (py += R.array[j2 * 3 + 1] * q),
34464
+ (pz += R.array[j2 * 3 + 2] * q),
34465
+ (nx += _.array[j2 * 3] * q),
34466
+ (ny += _.array[j2 * 3 + 1] * q),
34467
+ (nz += _.array[j2 * 3 + 2] * q);
34468
+ }
34469
+ let acc = new Map();
34470
+ if (x && P)
34471
+ for (let m of S) {
34472
+ let j2 = m.j,
34473
+ q = 1 / (m.d + 1e-6);
34474
+ for (let k4 = 0; k4 < 4; k4++) {
34475
+ let bone = x.array[j2 * 4 + k4],
34476
+ bw = P.array[j2 * 4 + k4] * q;
34477
+ acc.set(bone, (acc.get(bone) ?? 0) + bw);
34478
+ }
34479
+ }
34480
+ let bs = [...acc.entries()].sort((B, D) => D[1] - B[1]).slice(0, 4),
34481
+ wsum = bs.reduce((B, D) => B + D[1], 0) || 1;
34482
+ return {
34483
+ p: [px / ch, py / ch, pz / ch],
34484
+ n: [nx / ch, ny / ch, nz / ch],
34485
+ idx: bs.map((B) => B[0]),
34486
+ w: bs.map((B) => B[1] / wsum),
34487
+ };
34488
+ },
34489
+ Q = (u) => (u <= 0.04045 ? u / 12.92 : ((u + 0.055) / 1.055) ** 2.4),
34490
+ tone = null;
34491
+ try {
34492
+ let img = c.Wolf3D_Head?.material?.map?.image;
34493
+ if (img?.width) {
34494
+ let cv = document.createElement("canvas"),
34495
+ cx = cv.getContext("2d", { willReadFrequently: !0 });
34496
+ cv.width = img.width;
34497
+ cv.height = img.height;
34498
+ cx.drawImage(img, 0, 0);
34499
+ let [l0, m0, l1, m1] = $t.lids_uv,
34500
+ px = Math.round(((l0 + l1) / 2) * img.width),
34501
+ py = Math.round(((m0 + m1) / 2) * img.height),
34502
+ dtd = cx.getImageData(Math.max(0, px - 12), Math.max(0, py - 12), 24, 24).data,
34503
+ rr = 0,
34504
+ gg = 0,
34505
+ bb = 0,
34506
+ n5 = dtd.length / 4;
34507
+ for (let k5 = 0; k5 < dtd.length; k5 += 4) (rr += dtd[k5]), (gg += dtd[k5 + 1]), (bb += dtd[k5 + 2]);
34508
+ tone = new re(Q(rr / n5 / 255), Q(gg / n5 / 255), Q(bb / n5 / 255));
34509
+ }
34510
+ } catch {}
34511
+ let sk = tone ?? new re(0.6, 0.34, 0.26);
34512
+ {
34513
+ let ubc = i.material.onBeforeCompile,
34514
+ U1 = { value: new Ye(M, p, g, T) },
34515
+ U2 = { value: new ve(F, 0) };
34516
+ let V3 = (sh, cxA) => {
34517
+ ubc?.(sh, cxA);
34518
+ Object.assign(sh.uniforms, { uNeck: U1, uNeckF: U2 });
34519
+ sh.vertexShader = sh.vertexShader
34520
+ .replace("#include <common>", `#include <common>
34521
+ varying vec3 vBindN;`)
34522
+ .replace("#include <begin_vertex>", `#include <begin_vertex>
34523
+ vBindN = position;`);
34524
+ sh.fragmentShader = sh.fragmentShader
34525
+ .replace("#include <common>", `#include <common>
34526
+ varying vec3 vBindN; uniform vec4 uNeck; uniform vec2 uNeckF;`)
34527
+ .replace(
34528
+ "#include <clipping_planes_fragment>",
34529
+ `#include <clipping_planes_fragment>
34530
+ {
34531
+ float rel = abs(vBindN.x) / uNeck.z;
34532
+ if (vBindN.z > uNeckF.x && vBindN.y > uNeck.x - uNeck.y - 0.012 && vBindN.y < uNeck.x + 0.001) {
34533
+ if (uNeck.w > 0.5) { if ((uNeck.x - vBindN.y) > uNeck.y * rel) discard; }
34534
+ else { if ((uNeck.x - vBindN.y) > uNeck.y * sqrt(max(0.0, 1.0 - rel * rel))) discard; }
34535
+ }
34536
+ }`,
34537
+ );
34538
+ };
34539
+ ((i.material.onBeforeCompile = V3),
34540
+ (i.material.customProgramCacheKey = () => `ava-neck-cut-${i.material.uuid}`),
34541
+ (i.material.needsUpdate = !0));
34542
+ }
34543
+ let GX = 40,
34544
+ GY = 16,
34545
+ padX = 0.02,
34546
+ padY = 0.012,
34547
+ yUp = M + 0.0025,
34548
+ yBot = M - p - padY,
34549
+ gW = g + padX,
34550
+ pos = [],
34551
+ nor = [],
34552
+ js = [],
34553
+ ws = [],
34554
+ vm = [],
34555
+ insideM =
34556
+ y === "u"
34557
+ ? (xx, yy) => yy < M + 0.0025 - 1e-4 && yy > M - p * Math.max(0, 1 - (xx / g) ** 2) ** 0.5 - padY && Math.abs(xx) < gW
34558
+ : (xx, yy) => yy < M + 0.0025 - 1e-4 && yy > yBot && (M - yy) > (p * Math.abs(xx)) / g && Math.abs(xx) < gW;
34559
+ for (let gy = 0; gy < GY; gy++) {
34560
+ let vv = yUp - ((yUp - yBot) * gy) / (GY - 1);
34561
+ for (let gx = 0; gx < GX; gx++) {
34562
+ let uu = (-gW + (2 * gW * gx) / (GX - 1)),
34563
+ sm = se(uu, vv, 0),
34564
+ nx2 = sm.n,
34565
+ nn = (nx2[0] ** 2 + nx2[1] ** 2 + nx2[2] ** 2) ** 0.5 || 1;
34566
+ (pos.push(sm.p[0] - (nx2[0] / nn) * 0.0035, sm.p[1] - (nx2[1] / nn) * 0.0035, sm.p[2] - (nx2[2] / nn) * 0.0035),
34567
+ nor.push(nx2[0] / nn, nx2[1] / nn, nx2[2] / nn));
34568
+ for (let k6 = 0; k6 < 4; k6++) (js.push(sm.idx[k6] ?? 0), ws.push(sm.w[k6] ?? 0));
34569
+ vm.push(insideM(uu, vv) ? 1 : 0);
34570
+ }
34571
+ }
34572
+ let geo = new Et();
34573
+ (geo.setAttribute("position", new at(pos, 3)),
34574
+ geo.setAttribute("normal", new at(nor, 3)),
34575
+ x && P && (geo.setAttribute("skinIndex", new er(js, 4)), geo.setAttribute("skinWeight", new at(ws, 4))));
34576
+ let idx = [];
34577
+ for (let gy = 0; gy < GY - 1; gy++)
34578
+ for (let gx = 0; gx < GX - 1; gx++) {
34579
+ let a1 = gy * GX + gx,
34580
+ q1 = a1 + 1,
34581
+ v1 = a1 + GX,
34582
+ y1 = v1 + 1;
34583
+ if (vm[a1] && vm[q1] && vm[v1] && vm[y1]) idx.push(a1, v1, q1, q1, v1, y1);
34584
+ }
34585
+ geo.setIndex(idx);
34586
+ let mat = new tn({
34587
+ side: Yt,
34588
+ color: sk,
34589
+ roughness: 0.48,
34590
+ metalness: 0,
34591
+ specularIntensity: 0.4,
34592
+ sheen: 0.4,
34593
+ sheenColor: new re(1, 0.96, 0.92),
34594
+ sheenRoughness: 0.7,
34595
+ clearcoat: 0.05,
34596
+ clearcoatRoughness: 0.35,
34597
+ }),
34598
+ mk = new Bi(geo, mat);
34599
+ (t1(mat),
34600
+ mk.bind(i.skeleton, i.bindMatrix),
34601
+ mk.position.copy(i.position),
34602
+ mk.quaternion.copy(i.quaternion),
34603
+ mk.scale.copy(i.scale),
34604
+ mk.updateMatrix(),
34605
+ (mk.castShadow = !1),
34606
+ (mk.receiveShadow = !0),
34607
+ (mk.frustumCulled = !1),
34608
+ mk.matrixAutoUpdate && mk.updateMatrixWorld(!0),
34609
+ i.parent.add(mk));
34610
+ }
34611
  function vp(e, t = {}) {
34612
  let { studio: n = !0, materials: i = !0, shadows: s = !0 } = t,
34613
  { renderer: r, scene: o, armature: a } = e,
 
34813
  envMapIntensity: 0.35,
34814
  });
34815
  }
34816
+ mq(c);
34817
  a.traverse((h) => {
34818
  if (!h.isMesh) return;
34819
  for (let u of [h.material.map, h.material.normalMap, h.material.roughnessMap])
 
35160
  this._setStatus("processing"),
35161
  this.model.respond(t, e));
35162
  }
35163
+ sendText(e) {
35164
+ if (!this._closed && (e = (e ?? "").trim())) {
35165
+ if (this.turn) this._interrupt();
35166
+ (this.voice.beginTurn(),
35167
+ (this.turn = {
35168
+ id: this.nextTurnId++,
35169
+ finished: !1,
35170
+ reply: "",
35171
+ metric: {
35172
+ id: this.nextTurnId - 1,
35173
+ userSeconds: null,
35174
+ speechEndedAt: performance.now(),
35175
+ firstTextMs: null,
35176
+ firstAudioMs: null,
35177
+ replySeconds: 0,
35178
+ reply: "",
35179
+ tools: [],
35180
+ outcome: "streaming",
35181
+ },
35182
+ }),
35183
+ this.metrics.push(this.turn.metric),
35184
+ this.model.respond(this.turn.id, null, e),
35185
+ this.dispatchEvent(new CustomEvent("user-text", { detail: { text: e } })));
35186
+ }
35187
+ }
35188
  onText(e, t) {
35189
  let n = this.turn;
35190
  if (!n || n.id !== e) return;
 
35500
  w1(t);
35501
  break;
35502
  case "ready":
35503
+ if (((Es = !0), Aq(), Rn?.finished(), Gn)) Vp();
35504
  (xr?.configure(t.sampleRate),
35505
  Object.assign(rn, { device: t.device, modelSampleRate: t.sampleRate }),
35506
  (Sh.hidden = !0),
 
35622
  l = new uh({
35623
  voice: a,
35624
  model: {
35625
+ respond: (c, h, w) =>
35626
+ h != null
35627
+ ? ts.postMessage({ kind: "respond", id: c, audio: h }, [h.buffer])
35628
+ : ts.postMessage({ kind: "respond", id: c, text: w }),
35629
  abort: (c) => ts.postMessage({ kind: "abort", id: c }),
35630
  reset: () => ts.postMessage({ kind: "reset" }),
35631
  },
 
35655
  (yt.endOfReply(), Fp());
35656
  }),
35657
  l.addEventListener("interrupted", () => Ts.classList.remove("visible")),
35658
+ l.addEventListener("user-text", (c) => {
35659
+ if (Ln.subtitles) (Np(`Tú: ${c.detail.text}`), Fp(3200));
35660
+ }),
35661
  l.addEventListener("error", (c) => {
35662
  let { error: h } = c.detail;
35663
  if ((console.error("generation failed:", h), h.fatal))
 
35668
  no.classList.remove("active"),
35669
  ai("stop", "End conversation"),
35670
  Ia("connected"),
35671
+ Aq(),
35672
  yt.setConversationState("connected"));
35673
  }
35674
+ function Aq() {
35675
+ if (Es && Ht && typedPending) {
35676
+ let e = typedPending;
35677
+ ((typedPending = null), Ht.sendText(e));
35678
+ }
35679
+ }
35680
+ var typedPending = null,
35681
+ typedFormEl = on("#type-form"),
35682
+ typedInputEl = on("#type-input");
35683
+ typedFormEl?.addEventListener("submit", (e) => {
35684
+ e.preventDefault();
35685
+ let t = (typedInputEl.value ?? "").trim();
35686
+ if (!t) return;
35687
+ if (!Es) return Jt(vh(ws));
35688
+ if (Ht) {
35689
+ if (Ht.status === "closed") return;
35690
+ (Ht.sendText(t), (typedInputEl.value = ""));
35691
+ } else if (xh !== "busy") {
35692
+ ((typedPending = t), T1());
35693
+ } else typedPending = t;
35694
+ });
35695
  async function Wp(e = !1) {
35696
  let t = Ht;
35697
  if (((Ht = null), t?.close(), yt.setConversationState("idle"), Ts.classList.remove("visible"), !e)) Jt(Pa.idle);
index.html CHANGED
@@ -57,6 +57,15 @@
57
  <footer id="controls">
58
  <div id="progress" hidden><div id="progress-fill"></div></div>
59
  <div id="caption">LOADING…</div>
 
 
 
 
 
 
 
 
 
60
  <div id="buttons">
61
  <button id="main-btn" disabled>
62
  <span id="main-btn-label">Loading…</span>
 
57
  <footer id="controls">
58
  <div id="progress" hidden><div id="progress-fill"></div></div>
59
  <div id="caption">LOADING…</div>
60
+ <form id="type-form" autocomplete="off">
61
+ <input id="type-input" type="text" enterkeyhint="send" maxlength="2000" placeholder="O escríbele aquí…" />
62
+ <button id="send-btn" type="submit" aria-label="Enviar mensaje" title="Enviar">
63
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
64
+ <line x1="22" y1="2" x2="11" y2="13"></line>
65
+ <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
66
+ </svg>
67
+ </button>
68
+ </form>
69
  <div id="buttons">
70
  <button id="main-btn" disabled>
71
  <span id="main-btn-label">Loading…</span>
workers/model-worker.js CHANGED
@@ -7,6 +7,7 @@
7
  * main -> worker:
8
  * { kind: "load", model, system }
9
  * { kind: "respond", id, audio } a user turn, 16 kHz Float32
 
10
  * { kind: "abort", id } barge-in: stop generating this turn
11
  * { kind: "system", system } new persona / instructions
12
  * { kind: "reset" } forget the conversation
@@ -121,8 +122,12 @@ async function load({ model: modelId, system: sys }) {
121
  post({ kind: "failed", name: lastErr?.name ?? "Error", message: lastErr?.message ?? String(lastErr) });
122
  }
123
 
124
- /** One user turn. */
125
- async function respond({ id, audio }) {
 
 
 
 
126
  const controller = new AbortController();
127
  running.set(id, controller);
128
  const { signal } = controller;
@@ -137,7 +142,7 @@ async function respond({ id, audio }) {
137
  try {
138
  await prior;
139
  signal.throwIfAborted();
140
- history.push({ role: "user", content: [{ type: "audio", audio }] });
141
 
142
  for (;;) {
143
  const stream = session.generate(messages(), { generation_mode: "interleaved", signal });
 
7
  * main -> worker:
8
  * { kind: "load", model, system }
9
  * { kind: "respond", id, audio } a user turn, 16 kHz Float32
10
+ * { kind: "respond", id, text } a user turn typed in the chat box
11
  * { kind: "abort", id } barge-in: stop generating this turn
12
  * { kind: "system", system } new persona / instructions
13
  * { kind: "reset" } forget the conversation
 
122
  post({ kind: "failed", name: lastErr?.name ?? "Error", message: lastErr?.message ?? String(lastErr) });
123
  }
124
 
125
+ /** One user turn: spoken (audio) or typed (text). */
126
+ async function respond({ id, audio, text }) {
127
+ if (!audio && !text) {
128
+ post({ kind: "finished", id, reason: "noop" });
129
+ return;
130
+ }
131
  const controller = new AbortController();
132
  running.set(id, controller);
133
  const { signal } = controller;
 
142
  try {
143
  await prior;
144
  signal.throwIfAborted();
145
+ history.push(audio ? { role: "user", content: [{ type: "audio", audio }] } : { role: "user", content: text });
146
 
147
  for (;;) {
148
  const stream = session.generate(messages(), { generation_mode: "interleaved", signal });