Iona401 commited on
Commit
f315eb5
·
verified ·
1 Parent(s): b335cc3

sync latest frontend + README (social links, corrected tags)

Browse files
Files changed (5) hide show
  1. README.md +2 -2
  2. docs/social_post.md +55 -0
  3. docs/transcript.txt +1 -0
  4. static/app.js +40 -7
  5. static/view3d.js +2 -2
README.md CHANGED
@@ -30,7 +30,7 @@ Most translators show you a destination. **Lingo Bridge shows you the journey.**
30
 
31
  ## 🎬 Demo
32
 
33
- ▶ **[Demo video](docs/demo.mp4)** · 📣 **[Social post](https://x.com/)** _(add link)_
34
 
35
  ![Lingo Bridge](docs/poster.png)
36
 
@@ -79,6 +79,6 @@ python3 app.py
79
  - **REQ-01 ≤32B/model** — Qwen3-4B + VoxCPM2 (2B). ✓
80
  - **REQ-02 Gradio Space in the org** — Docker Space `build-small-hackathon/lingo-bridge`. ✓
81
  - **REQ-03 Demo video** — [docs/demo.mp4](docs/demo.mp4). ✓
82
- - **REQ-04 Social post** — _add the link above._
83
  - **REQ-05 ZeroGPU limit** — n/a (GPU on Modal, not ZeroGPU). ✓
84
  - **REQ-06 README tags + write-up** — above. ✓
 
30
 
31
  ## 🎬 Demo
32
 
33
+ ▶ **[Demo video](docs/demo.mp4)** · 📣 **Social posts:** [X/Twitter](https://x.com/auracanvas/status/2066666490871558485) · [Bluesky](https://bsky.app/profile/auracanvas.bsky.social/post/3moehclek7k26)
34
 
35
  ![Lingo Bridge](docs/poster.png)
36
 
 
79
  - **REQ-01 ≤32B/model** — Qwen3-4B + VoxCPM2 (2B). ✓
80
  - **REQ-02 Gradio Space in the org** — Docker Space `build-small-hackathon/lingo-bridge`. ✓
81
  - **REQ-03 Demo video** — [docs/demo.mp4](docs/demo.mp4). ✓
82
+ - **REQ-04 Social post** — [X](https://x.com/auracanvas/status/2066666490871558485) · [Bluesky](https://bsky.app/profile/auracanvas.bsky.social/post/3moehclek7k26).
83
  - **REQ-05 ZeroGPU limit** — n/a (GPU on Modal, not ZeroGPU). ✓
84
  - **REQ-06 README tags + write-up** — above. ✓
docs/social_post.md ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Social posts — Lingo Bridge (copy & post, then send me the URL)
2
+
3
+ Space: https://huggingface.co/spaces/build-small-hackathon/lingo-bridge
4
+ 📎 Attach the demo video (`docs/demo.mp4`) to the post — it's the whole pitch.
5
+
6
+ ---
7
+
8
+ ## ▶ Option A — X / Twitter (single post, ~280 chars)
9
+
10
+ 🌉 Most translators show you the destination. Lingo Bridge shows you the *journey*.
11
+
12
+ Watch — and HEAR — a sentence morph into another language across 7 layers: meaning crosses first, then grammar, then word order, in 3D.
13
+
14
+ All small, local models. #BuildSmall
15
+
16
+ https://huggingface.co/spaces/build-small-hackathon/lingo-bridge
17
+
18
+ ---
19
+
20
+ ## 🧵 Option B — X / Twitter thread
21
+
22
+ 1/ 🌉 Lingo Bridge: don't just translate a sentence — *watch and hear it become* another language.
23
+
24
+ Built for the @huggingface × @Gradio #BuildSmall hackathon. 7 stages, 3D, spoken at every step. 👇
25
+ https://huggingface.co/spaces/build-small-hackathon/lingo-bridge
26
+
27
+ 2/ How it works: one small text model (Qwen3-4B) breaks the sentence into phrase "units" and aligns them. Then 7 layers build deterministically — meaning flips first, then actions, time, grammar, and finally the word order rearranges into crossing ribbons. 🟪→🟦
28
+
29
+ 3/ Every layer is spoken aloud by @OpenBMB's VoxCPM2 (built on MiniCPM-4) — 48kHz, 10 languages. The whole thing runs on @modal GPUs and scales to zero. Tiny models, big toy. 🐜
30
+
31
+ 4/ Off-the-shelf translators give you an answer. This gives you the *transformation* — a little language toy you can watch think. Try it 👇
32
+ https://huggingface.co/spaces/build-small-hackathon/lingo-bridge
33
+
34
+ ---
35
+
36
+ ## 💼 Option C — LinkedIn
37
+
38
+ 🌉 I built **Lingo Bridge** for the Hugging Face × Gradio "Build Small" hackathon.
39
+
40
+ Most translators show you the destination. Lingo Bridge shows you the *journey*: it turns one sentence into a 7-stage transformation from one language to another — meaning crosses first, then actions, then time words, then grammar, and finally the word order rearranges — rendered as an interactive 3D card stack and **spoken aloud at every stage**.
41
+
42
+ It runs entirely on small, open models (each well under 32B):
43
+ • Qwen3-4B (text decomposition) via llama.cpp
44
+ • OpenBMB VoxCPM2 — built on MiniCPM-4 — for speech, in 10 languages
45
+ • Served on Modal GPUs (scale-to-zero), behind a custom Three.js UI in a Gradio Space
46
+
47
+ Try it: https://huggingface.co/spaces/build-small-hackathon/lingo-bridge
48
+
49
+ #BuildSmall #HuggingFace #Gradio #SmallModels #AI
50
+
51
+ ---
52
+
53
+ ## Hashtags / handles to consider
54
+ #BuildSmall #HuggingFace #Gradio #SmallModels #LocalAI
55
+ @huggingface @Gradio @OpenBMB @modal
docs/transcript.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Meet Lingo Bridge, where a sentence becomes another language right before your eyes. Instead of one sudden jump, meaning crosses over first, then actions, time words, and grammar follow, and finally the word order rearranges into place. Each stage appears as a glowing card in an interactive 3D stack, and every step is spoken aloud so you can hear the language take shape. It runs on a compact Qwen language model and the VoxCPM speech engine, all served from the cloud. Explore the layers, orbit the stack, and watch translation unfold one bridge at a time.
static/app.js CHANGED
@@ -145,7 +145,10 @@ const hooks = {
145
  state.hover = unit;
146
  state.cards.setHover(unit);
147
  },
148
- onPlay: (layerIdx) => playLayer(layerIdx),
 
 
 
149
  onPerspectiveChange: (t) => {
150
  // Called by CardsView when animating perspective automatically
151
  const slider = $("#perspective-slider");
@@ -194,6 +197,8 @@ function showLoader(on, msg) {
194
 
195
  // ---- audio -----------------------------------------------------------------
196
  const player = $("#player");
 
 
197
  function langForLayer(idx) {
198
  if (!state.data) return "English";
199
  return idx === 0 ? state.data.source_lang : state.data.target_lang;
@@ -208,6 +213,12 @@ async function ttsUrl(text, lang) {
208
  }
209
  async function playLayer(idx) {
210
  if (!state.data) return;
 
 
 
 
 
 
211
  const layer = state.data.layers[idx];
212
  highlightLayer(idx);
213
  // Precomputed examples ship per-layer audio -> play instantly, no TTS call.
@@ -215,7 +226,9 @@ async function playLayer(idx) {
215
  player.src = url;
216
  await player.play().catch(() => {});
217
  return new Promise((res) => {
 
218
  player.onended = () => {
 
219
  if (!state.playing) {
220
  highlightLayer(-1);
221
  }
@@ -226,19 +239,39 @@ async function playLayer(idx) {
226
  function highlightLayer(idx) {
227
  state.cards.highlightLayer(idx);
228
  }
 
 
 
 
 
 
 
 
 
 
 
 
229
  async function playAll() {
230
- if (!state.data || state.playing) return;
 
 
 
 
 
231
  state.playing = true;
232
  $("#playall").classList.add("playing");
233
- $("#playall").textContent = "■ Playing…";
234
  for (let i = 0; i < state.data.layers.length; i++) {
235
  if (!state.playing) break;
236
  await playLayer(i);
237
  }
238
- state.playing = false;
239
- $("#playall").classList.remove("playing");
240
- $("#playall").textContent = "▶ Play all layers";
241
- highlightLayer(-1);
 
 
 
242
  }
243
 
244
  boot();
 
145
  state.hover = unit;
146
  state.cards.setHover(unit);
147
  },
148
+ onPlay: (layerIdx) => {
149
+ if (state.playing) stopPlaying();
150
+ playLayer(layerIdx);
151
+ },
152
  onPerspectiveChange: (t) => {
153
  // Called by CardsView when animating perspective automatically
154
  const slider = $("#perspective-slider");
 
197
 
198
  // ---- audio -----------------------------------------------------------------
199
  const player = $("#player");
200
+ let currentPlayResolve = null;
201
+
202
  function langForLayer(idx) {
203
  if (!state.data) return "English";
204
  return idx === 0 ? state.data.source_lang : state.data.target_lang;
 
213
  }
214
  async function playLayer(idx) {
215
  if (!state.data) return;
216
+
217
+ if (currentPlayResolve) {
218
+ currentPlayResolve();
219
+ currentPlayResolve = null;
220
+ }
221
+
222
  const layer = state.data.layers[idx];
223
  highlightLayer(idx);
224
  // Precomputed examples ship per-layer audio -> play instantly, no TTS call.
 
226
  player.src = url;
227
  await player.play().catch(() => {});
228
  return new Promise((res) => {
229
+ currentPlayResolve = res;
230
  player.onended = () => {
231
+ currentPlayResolve = null;
232
  if (!state.playing) {
233
  highlightLayer(-1);
234
  }
 
239
  function highlightLayer(idx) {
240
  state.cards.highlightLayer(idx);
241
  }
242
+ function stopPlaying() {
243
+ state.playing = false;
244
+ player.pause();
245
+ player.src = "";
246
+ if (currentPlayResolve) {
247
+ currentPlayResolve();
248
+ currentPlayResolve = null;
249
+ }
250
+ $("#playall").classList.remove("playing");
251
+ $("#playall").textContent = "▶ Play all layers";
252
+ highlightLayer(-1);
253
+ }
254
  async function playAll() {
255
+ if (!state.data) return;
256
+ if (state.playing) {
257
+ stopPlaying();
258
+ return;
259
+ }
260
+
261
  state.playing = true;
262
  $("#playall").classList.add("playing");
263
+ $("#playall").textContent = "■ Stop playing";
264
  for (let i = 0; i < state.data.layers.length; i++) {
265
  if (!state.playing) break;
266
  await playLayer(i);
267
  }
268
+
269
+ if (state.playing) {
270
+ state.playing = false;
271
+ $("#playall").classList.remove("playing");
272
+ $("#playall").textContent = "▶ Play all layers";
273
+ highlightLayer(-1);
274
+ }
275
  }
276
 
277
  boot();
static/view3d.js CHANGED
@@ -1,6 +1,6 @@
1
  import * as THREE from "three";
2
  import { OrbitControls } from "three/addons/controls/OrbitControls.js";
3
- import { mixRGB } from "/static/app.js?v=19";
4
 
5
  const col = (mix) => {
6
  const [r, g, b] = mixRGB(mix);
@@ -966,7 +966,7 @@ export class CardsView {
966
 
967
  // Zoom in closer
968
  const { minRadius } = this._getRadii();
969
- this.targetCamRadius = minRadius + 5.0; // Zoom in nicely to frame the focused row
970
  }
971
 
972
  _resize() {
 
1
  import * as THREE from "three";
2
  import { OrbitControls } from "three/addons/controls/OrbitControls.js";
3
+ import { mixRGB } from "/static/app.js?v=20";
4
 
5
  const col = (mix) => {
6
  const [r, g, b] = mixRGB(mix);
 
966
 
967
  // Zoom in closer
968
  const { minRadius } = this._getRadii();
969
+ this.targetCamRadius = minRadius + 11.0; // Zoom in nicely to frame the focused row, keeping it slightly zoomed out per user preference
970
  }
971
 
972
  _resize() {