ameforge commited on
Commit
518ae01
·
verified ·
1 Parent(s): e7538e5

KEEP Learn: separate Space, English UI, modern design, SVG icons

Browse files
Files changed (1) hide show
  1. app.py +89 -0
app.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, json, sys, numpy as np, gradio as gr, torch
2
+ from huggingface_hub import snapshot_download
3
+
4
+ PRIVATE = os.environ.get("MODEL_REPO", "")
5
+ d = snapshot_download(PRIVATE, token=os.environ.get("HF_TOKEN")) if PRIVATE else os.path.dirname(os.path.abspath(__file__))
6
+ sys.path.insert(0, d)
7
+ import keep_audio_core as K
8
+ cfg = json.load(open(os.path.join(d, "config.json"))) if os.path.exists(os.path.join(d, "config.json")) else {}
9
+ R = K.Recognizer(os.path.join(d, "audio2b_KEEP_graine0.pt"), os.path.join(d, "audio2b_normalisation.pt"), gain=cfg.get("gain", "peak"))
10
+
11
+ CSS = """
12
+ @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@600;700&family=Space+Grotesk:wght@400;500;600&display=swap');
13
+ .gradio-container { max-width: 920px !important; margin: auto !important; background: #F5F4F0 !important; font-family: 'Space Grotesk', system-ui, sans-serif !important; }
14
+ .hero { background: linear-gradient(135deg, #0D1B2A 0%, #1B3A5C 100%); border-radius: 22px; padding: 34px 32px 26px; color: #fff; margin-bottom: 22px; box-shadow: 0 12px 40px rgba(13,27,42,.25); }
15
+ .hero .hero-top { display: flex; gap: 18px; align-items: flex-start; }
16
+ .hero .hero-icon { flex: 0 0 auto; width: 56px; height: 56px; border-radius: 16px; background: rgba(201,162,39,.18); border: 1px solid rgba(201,162,39,.5); display: flex; align-items: center; justify-content: center; color: #C9A227; }
17
+ .hero h1 { font-family: 'Cormorant Garamond', Georgia, serif; font-size: 46px; margin: 0 0 6px; font-weight: 700; letter-spacing: -.5px; line-height: 1.05; }
18
+ .hero .tagline { font-size: 16.5px; opacity: .92; margin: 0 0 16px; line-height: 1.5; }
19
+ .hero .pills { display: flex; flex-wrap: wrap; gap: 8px; }
20
+ .hero .pill { background: rgba(255,255,255,.10); border: 1px solid rgba(255,255,255,.22); border-radius: 999px; padding: 6px 14px; font-size: 13.5px; }
21
+ .hero .pill b { color: #C9A227; }
22
+ button.primary { background: #0D1B2A !important; border-radius: 12px !important; }
23
+ .footnote { color: #64748b; font-size: 13.5px; line-height: 1.55; margin-top: 18px; }
24
+ """
25
+
26
+ # Simple, professional mark: a plus sign for "learning something new", drawn as SVG, not an emoji.
27
+ PLUS_SVG = """<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>"""
28
+
29
+ HERO = f"""<div class="hero"><div class="hero-top"><div class="hero-icon">{PLUS_SVG}</div><div>
30
+ <h1>KEEP Learn</h1>
31
+ <p class="tagline">Teach KEEP words it has <b>never heard</b>, live, with no retraining: it keeps the fingerprint of your examples, then recognizes by resemblance.</p>
32
+ </div></div><div class="pills">
33
+ <span class="pill"><b>2–5</b> words per session</span>
34
+ <span class="pill"><b>1–3</b> examples per word</span>
35
+ <span class="pill">Session only — forgotten on close</span>
36
+ </div></div>"""
37
+
38
+ def summary(mem):
39
+ if not mem: return "_No word learned yet._"
40
+ return "**Learned words:** " + " · ".join(f"{w} ({len(v)} example{'s' if len(v) > 1 else ''})" for w, v in mem.items())
41
+
42
+ def add_example(name, audio, gain_choice, mem):
43
+ mem = dict(mem or {}); name = (name or "").strip().lower()
44
+ if not name: return mem, summary(mem), "Type the word name first.", gr.update()
45
+ if audio is None: return mem, summary(mem), "Record the word first.", gr.update()
46
+ R.gain = gain_choice; sr, wav = audio
47
+ mem[name] = list(mem.get(name, [])) + [R.embedding(wav, sr).tolist()]
48
+ n = len(mem[name]); tip = " Add 1 or 2 more examples to make it more sure." if n < 3 else ""
49
+ return mem, summary(mem), f"Example no. {n} of “{name}” memorized.{tip}", None
50
+
51
+ def forget_all(): return {}, summary({}), "Everything forgotten.", None
52
+
53
+ def recognize(audio, gain_choice, mem):
54
+ if not mem or len(mem) < 2: return None, "Teach it at least **2 words** first (field above)."
55
+ if audio is None: return None, "Say one of the learned words."
56
+ R.gain = gain_choice; sr, wav = audio; q = R.embedding(wav, sr)
57
+ names = list(mem); P = torch.nn.functional.normalize(torch.stack([torch.tensor(mem[w]).mean(0) for w in names]), dim=-1)
58
+ sim = (P @ q).numpy(); p = np.exp(20 * (sim - sim.max())); p = p / p.sum()
59
+ i = int(sim.argmax()); j = np.argsort(-sim)[1] if len(sim) > 1 else i
60
+ gap = sim[i] - sim[j]
61
+ msg = (f"I heard: **{names[i]}**." if gap > 0.05 else f"I'm hesitating… rather **{names[i]}**, but it also sounds like “{names[j]}”. Add examples.")
62
+ return {w: float(x) for w, x in zip(names, p)}, msg
63
+
64
+ with gr.Blocks(title="KEEP Learn", css=CSS) as demo:
65
+ gr.HTML(HERO)
66
+ gr.Markdown("**1.** Type a name, record the word (1 to 3 times) and click “Memorize”. Do this for 2 to 5 words.\n"
67
+ "**2.** Below, say one of the learned words again: KEEP tells you which one it recognizes.")
68
+ gain_radio = gr.Radio(["peak", "none"], value=cfg.get("gain", "peak"), label="Volume setting (peak = automatic, none = raw)")
69
+ mem = gr.State({})
70
+ with gr.Row():
71
+ name = gr.Textbox(label="Word name", placeholder="e.g.: bonjour")
72
+ ex_audio = gr.Audio(sources=["microphone", "upload"], type="numpy", label="Say this word")
73
+ with gr.Row():
74
+ b_add = gr.Button("Memorize this example", variant="primary"); b_forget = gr.Button("Forget everything")
75
+ info = gr.Markdown(); learned = gr.Markdown(summary({}))
76
+ b_add.click(add_example, [name, ex_audio, gain_radio, mem], [mem, learned, info, ex_audio])
77
+ b_forget.click(forget_all, None, [mem, learned, info, ex_audio])
78
+ gr.Markdown("---")
79
+ with gr.Row():
80
+ q_audio = gr.Audio(sources=["microphone", "upload"], type="numpy", label="Say one of the learned words again")
81
+ q_lab = gr.Label(label="Resemblance to each learned word")
82
+ q_msg = gr.Markdown()
83
+ gr.Button("What did I say?", variant="primary").click(recognize, [q_audio, gain_radio, mem], [q_lab, q_msg])
84
+ gr.Markdown("_Experimental: what it learns here is forgotten when you close the page. "
85
+ "It works best with the same voice as the examples, and 2 to 3 examples per word._",
86
+ elem_classes=["footnote"])
87
+ gr.Markdown("**Limits.** It recognizes word sounds, it does not understand speech. A noisy microphone or words that sound very alike can lower accuracy.",
88
+ elem_classes=["footnote"])
89
+ demo.launch()