Humuhumu33 commited on
Commit
763e76a
·
verified ·
1 Parent(s): 8dd57e9

Unify: q-live redirects to canonical Q (text+voice) at HOLOGRAMTECH/q

Browse files
Files changed (1) hide show
  1. index.html +14 -164
index.html CHANGED
@@ -2,175 +2,25 @@
2
  <html lang="en">
3
  <head>
4
  <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no">
6
  <title>Q</title>
7
- <script type="importmap">
8
- { "imports": {
9
- "@huggingface/transformers": "/_shared/voice/vendor/kokoro/transformers/transformers.js",
10
- "phonemizer": "/_shared/voice/vendor/kokoro/phonemizer.js",
11
- "fs/promises": "/_shared/voice/vendor/kokoro/stub.js",
12
- "path": "/_shared/voice/vendor/kokoro/stub.js"
13
- } }
14
- </script>
15
  <style>
16
- :root { --bg:#06070c; --fg:#eef1f8; --dim:#7b849c; }
17
- * { box-sizing:border-box; margin:0; padding:0; -webkit-tap-highlight-color:transparent; }
18
- html,body { height:100%; overflow:hidden; }
19
- body { background:radial-gradient(130% 100% at 50% 42%, #0e1220 0%, var(--bg) 70%); color:var(--fg);
20
- font:400 17px/1.5 -apple-system,"Segoe UI",Inter,system-ui,sans-serif; -webkit-font-smoothing:antialiased;
21
- display:flex; flex-direction:column; align-items:center; justify-content:center; height:100dvh; user-select:none; }
22
- .wordmark { position:fixed; top:22px; left:24px; font-size:13px; letter-spacing:.22em; color:var(--dim); font-weight:600; }
23
- .wordmark b { color:var(--fg); }
24
- #stage { position:relative; width:min(74vw,340px); height:min(74vw,340px); cursor:pointer; display:grid; place-items:center; }
25
- canvas { width:100%; height:100%; display:block; }
26
- .hint { position:absolute; font-size:13px; letter-spacing:.14em; text-transform:uppercase; color:var(--dim);
27
- pointer-events:none; transition:opacity .4s; }
28
- .caption { margin-top:40px; max-width:min(88vw,560px); text-align:center; min-height:3.2em; padding:0 8px; }
29
- .caption .you { color:var(--dim); font-size:16px; }
30
- .caption .q { color:var(--fg); font-size:21px; line-height:1.45; letter-spacing:-.01em; }
31
- .caption.fade { opacity:0; transition:opacity 1.2s; }
32
- .status { margin-top:6px; font-size:12px; letter-spacing:.16em; text-transform:uppercase; color:var(--dim); min-height:16px; }
33
- .compose { position:fixed; bottom:max(20px,env(safe-area-inset-bottom)); left:50%; transform:translateX(-50%);
34
- width:min(90vw,520px); display:flex; gap:8px; opacity:.5; transition:opacity .2s; }
35
- .compose:focus-within { opacity:1; }
36
- .compose input { flex:1; background:rgba(255,255,255,.04); border:1px solid rgba(255,255,255,.08); color:var(--fg);
37
- border-radius:14px; padding:11px 15px; font-size:15px; outline:none; }
38
- .compose input::placeholder { color:#465065; }
39
- .compose button { background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.1); color:var(--fg);
40
- border-radius:14px; padding:0 16px; font-size:15px; cursor:pointer; }
41
- .lat { position:fixed; bottom:16px; right:18px; font:11px ui-monospace,Menlo,monospace; color:#333c52; letter-spacing:.04em; text-align:right; }
42
- .lat b { color:#20d17a; font-weight:600; } /* speculated (fast) turn */
43
- .stats { position:fixed; bottom:16px; left:18px; font:10.5px/1.5 ui-monospace,Menlo,monospace; color:#333c52; letter-spacing:.02em;
44
- white-space:pre; opacity:0; transition:opacity .3s; }
45
- .stats.on { opacity:1; }
46
  </style>
47
  </head>
48
  <body>
49
- <div class="wordmark"><b>Q</b></div>
50
-
51
- <div id="stage" title="tap to talk">
52
- <canvas id="orb" width="680" height="680"></canvas>
53
- <div class="hint" id="hint">tap to talk</div>
54
  </div>
55
-
56
- <div class="caption" id="cap"></div>
57
- <div class="status" id="status"></div>
58
-
59
- <form class="compose" id="compose" autocomplete="off">
60
- <input id="text" placeholder="or type to Q" enterkeyhint="send">
61
- <button type="submit">Say</button>
62
- </form>
63
- <div class="lat" id="lat"></div>
64
- <div class="stats" id="stats"></div>
65
-
66
- <script>
67
- if ("serviceWorker" in navigator) { try { navigator.serviceWorker.register("/q-live-sw.js", { scope: "/" }); } catch (e) {} }
68
- </script>
69
- <script type="module">
70
- import { createQLive } from "/apps/q/q-live.mjs";
71
-
72
- const $ = (id) => document.getElementById(id);
73
- const q = createQLive({ persona: "You are Q, a private on-device AI that runs entirely in this browser. Answer in one or two short, warm, natural spoken sentences." });
74
- window.Q = q;
75
-
76
- let phase = "idle", loading = false, level = 0, capFadeT = 0;
77
- const setStatus = (s) => $("status").textContent = s;
78
- const showHint = (on) => $("hint").style.opacity = on ? 1 : 0;
79
-
80
- function caption(who, text) {
81
- const el = $("cap"); el.classList.remove("fade");
82
- el.innerHTML = `<span class="${who}">${text || ""}</span>`;
83
- capFadeT = performance.now() + 6000; // fade if nothing new for a while
84
- }
85
-
86
- q.on("state", (s) => { phase = s;
87
- showHint(s === "idle" && !q.active && !loading);
88
- setStatus(loading ? "waking" : s === "idle" ? (q.active ? "" : "") : s);
89
- });
90
- q.on("partial", (t) => caption("you", t + " …"));
91
- q.on("final", (t) => caption("you", t));
92
- q.on("spoken", (t) => caption("q", t)); // Q's words, revealed IN SYNC with the spoken voice (audio-clock timed)
93
- // ── per-stage latency ledger: the references' real lesson is that MAGIC IS CONSISTENCY (P95), not a good average.
94
- // Accumulate first-audio per turn, split COLD vs SPECULATED, and render median + P95 so the win is provable. ──
95
- const lard = { fa_cold: [], fa_spec: [], ft_cold: [], ft_warm: [] }; // fa = first-audio, ft = first-token (prefill)
96
- const pct = (a, p) => { if (!a.length) return null; const s = [...a].sort((x, y) => x - y); return s[Math.min(s.length - 1, Math.floor(p / 100 * s.length))]; };
97
- function renderStats() {
98
- const row = (name, a) => { if (!a.length) return null; return name.padEnd(11) + " n=" + String(a.length).padEnd(3) + " p50 " + String(pct(a, 50)).padStart(5) + " p95 " + String(pct(a, 95)).padStart(5) + " ms"; };
99
- const lines = [
100
- row("audio·spec", lard.fa_spec), row("audio·cold", lard.fa_cold),
101
- row("1sttok·warm", lard.ft_warm), row("1sttok·cold", lard.ft_cold),
102
- ].filter(Boolean);
103
- const el = $("stats"); el.textContent = lines.join("\n"); el.classList.toggle("on", lines.length > 0);
104
- }
105
- q.on("metrics", (m) => {
106
- if (m.firstAudio != null && !m.cached) { // live first-audio readout (emitted mid-turn; don't record here)
107
- const fast = !!m.speculated;
108
- $("lat").innerHTML = fast ? "<b>spec · " + m.firstAudio + " ms to voice</b>" : m.firstAudio + " ms to voice";
109
- } else if (m.cached && m.firstAudio != null) { $("lat").innerHTML = "cached · " + m.firstAudio + " ms to voice"; }
110
- if (m.total == null) return; // record into the ledger ONCE, at turn completion (metrics is emitted repeatedly per turn)
111
- if (m.cached) return;
112
- if (m.firstToken != null) (m.warm ? lard.ft_warm : lard.ft_cold).push(m.firstToken); // the warm-KV win lands HERE (prefill)
113
- if (m.firstAudio != null) (m.speculated ? lard.fa_spec : lard.fa_cold).push(m.firstAudio);
114
- renderStats();
115
- });
116
- q.on("speculating", (t) => console.log("[Q] speculating on:", t));
117
- q.on("warn", (w) => console.warn("[Q]", w));
118
-
119
- // ── the orb: one living, audio-reactive presence ──
120
- const cv = $("orb"), g = cv.getContext("2d"), W = cv.width, H = cv.height, cx = W/2, cy = H/2;
121
- const PAL = {
122
- idle: ["#3a5cff", "#6ea8ff"],
123
- listening: ["#18b6ff", "#7ce0ff"],
124
- thinking: ["#8a5cff", "#c19bff"],
125
- speaking: ["#20d17a", "#82f0b4"],
126
- waking: ["#8a5cff", "#c19bff"],
127
- };
128
- function draw(t) {
129
- const target = q.getLevel ? q.getLevel() : 0;
130
- level += (target - level) * 0.25; // smooth
131
- const st = loading ? "waking" : phase;
132
- const col = PAL[st] || PAL.idle;
133
- const breathe = 1 + 0.03 * Math.sin(t/900);
134
- const react = 1 + level * 0.9; // voice-reactive swell
135
- const R0 = (W * 0.16) * breathe * react;
136
- g.clearRect(0,0,W,H);
137
- // soft aura layers
138
- for (let i = 4; i >= 0; i--) {
139
- const r = R0 + i * (W*0.05) + (i===0 ? level*40 : 0);
140
- const grd = g.createRadialGradient(cx, cy, r*0.15, cx, cy, r);
141
- grd.addColorStop(0, col[1] + (i===0 ? "e6" : "1c"));
142
- grd.addColorStop(1, col[0] + "00");
143
- g.beginPath(); g.fillStyle = grd; g.arc(cx, cy, r, 0, 7); g.fill();
144
- }
145
- // bright core
146
- const core = g.createRadialGradient(cx, cy, 0, cx, cy, R0*0.6);
147
- core.addColorStop(0, "#ffffff" + (st==="idle" ? "cc" : "ee"));
148
- core.addColorStop(1, col[1] + "00");
149
- g.beginPath(); g.fillStyle = core; g.arc(cx, cy, R0*0.6, 0, 7); g.fill();
150
- // caption fade when idle+quiet
151
- if (capFadeT && performance.now() > capFadeT && phase === "idle") { $("cap").classList.add("fade"); capFadeT = 0; }
152
- requestAnimationFrame(draw);
153
- }
154
- requestAnimationFrame(draw);
155
-
156
- // ── one gesture: tap the orb to start / end the call ──
157
- async function toggle() {
158
- if (loading) return;
159
- if (q.active) { q.stop(); setStatus(""); showHint(true); return; }
160
- try { q.arm(); } catch (e) {} // UNLOCK audio inside the gesture (must be synchronous)
161
- loading = true; setStatus("waking"); showHint(false);
162
- try { await q.start(); } catch (e) { caption("q", "I couldn't start — " + (e.message || e)); }
163
- loading = false;
164
- }
165
- $("stage").addEventListener("click", toggle);
166
-
167
- // text drive (no mic) — same brain→voice path
168
- $("compose").addEventListener("submit", async (e) => {
169
- e.preventDefault(); const v = $("text").value.trim(); if (!v) return; $("text").value = "";
170
- try { q.arm(); } catch (e2) {} // unlock audio inside the submit gesture
171
- caption("you", v);
172
- try { await q.say(v); } catch (err) { caption("q", "…" + (err.message || err)); }
173
- });
174
- </script>
175
  </body>
176
  </html>
 
2
  <html lang="en">
3
  <head>
4
  <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
  <title>Q</title>
7
+ <meta http-equiv="refresh" content="0; url=https://hologramtech-q.static.hf.space">
8
+ <link rel="canonical" href="https://hologramtech-q.static.hf.space">
 
 
 
 
 
 
9
  <style>
10
+ html,body{height:100%;margin:0}
11
+ body{background:#0b0f14;color:#e8eef5;font:16px/1.5 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
12
+ display:flex;align-items:center;justify-content:center;text-align:center}
13
+ a{color:#8ecbff}
14
+ .c{max-width:32ch}
15
+ .d{opacity:.6;font-size:14px;margin-top:.75em}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  </style>
17
  </head>
18
  <body>
19
+ <div class="c">
20
+ <p>Q now lives in one place — text and voice together.</p>
21
+ <p><a href="https://hologramtech-q.static.hf.space">Open Q →</a></p>
22
+ <p class="d">Redirecting…</p>
 
23
  </div>
24
+ <script>location.replace("https://hologramtech-q.static.hf.space");</script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  </body>
26
  </html>