Spaces:
Paused
Paused
Complete redesign: voice-first shell, pure black, numbered steps, underline input, no cards; logic unchanged
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 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", "")
|
|
@@ -8,161 +8,187 @@ 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=
|
| 13 |
-
body { background: #
|
| 14 |
.gradio-container {
|
| 15 |
-
--body-background-fill: #
|
| 16 |
-
--background-fill-primary: #
|
| 17 |
-
--background-fill-secondary: #
|
| 18 |
-
--block-background-fill:
|
| 19 |
-
--block-border-color:
|
| 20 |
-
--block-label-background-fill:
|
| 21 |
-
--block-label-text-color: #
|
| 22 |
-
--block-title-text-color: #
|
| 23 |
-
--body-text-color: #
|
| 24 |
-
--body-text-color-subdued: #
|
| 25 |
-
--button-primary-background-fill: #
|
| 26 |
--button-primary-background-fill-hover: #FFFFFF;
|
| 27 |
-
--button-primary-text-color: #
|
| 28 |
-
--button-secondary-background-fill:
|
| 29 |
-
--button-secondary-background-fill-hover: #
|
| 30 |
-
--button-secondary-text-color: #
|
| 31 |
-
--button-secondary-border-color: rgba(255,255,255,.
|
| 32 |
-
--input-background-fill:
|
| 33 |
-
--input-border-color: rgba(255,255,255,.
|
| 34 |
-
--input-text-color: #
|
| 35 |
-
--input-placeholder-color: #
|
| 36 |
-
max-width:
|
|
|
|
|
|
|
| 37 |
}
|
| 38 |
-
.
|
| 39 |
-
.
|
| 40 |
-
.
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
.hero .tagline b, .hero .tagline strong { color: #E5C15D !important; }
|
| 44 |
-
.hero .pills { display: flex; flex-wrap: wrap; gap: 8px; }
|
| 45 |
-
.hero .pill { background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.12); border-radius: 999px; padding: 6px 14px; font-size: 13.5px; color: #FFFFFF !important; }
|
| 46 |
-
.hero .pill b { color: #E5C15D; }
|
| 47 |
-
.gradio-container .block,
|
| 48 |
-
.gradio-container .form,
|
| 49 |
-
.gradio-container .wrap,
|
| 50 |
-
.gradio-container .panel,
|
| 51 |
-
.gradio-container .component,
|
| 52 |
-
.gradio-container .audio,
|
| 53 |
-
.gradio-container .plot,
|
| 54 |
-
.gradio-container .input,
|
| 55 |
-
.gradio-container .output {
|
| 56 |
-
border-radius: 18px !important;
|
| 57 |
-
overflow: hidden !important;
|
| 58 |
-
border: 1px solid rgba(255,255,255,.09) !important;
|
| 59 |
-
background: #141A26 !important;
|
| 60 |
-
box-shadow: 0 4px 24px rgba(0,0,0,.35) !important;
|
| 61 |
}
|
| 62 |
-
.gradio-container
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
background: transparent !important;
|
| 66 |
-
border: none !important;
|
| 67 |
-
box-shadow: none !important;
|
| 68 |
}
|
| 69 |
-
.gradio-container
|
| 70 |
-
.gradio-container
|
| 71 |
-
.
|
| 72 |
-
.
|
| 73 |
-
.
|
| 74 |
-
.
|
| 75 |
-
.
|
| 76 |
-
.
|
| 77 |
-
.
|
| 78 |
-
.
|
| 79 |
-
.
|
| 80 |
-
.
|
| 81 |
-
.
|
| 82 |
-
.
|
| 83 |
-
.
|
| 84 |
-
.
|
| 85 |
-
.
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
.
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
}
|
| 95 |
-
.gradio-container
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
}
|
| 99 |
-
.gradio-container
|
| 100 |
-
|
| 101 |
-
|
|
|
|
| 102 |
}
|
|
|
|
|
|
|
|
|
|
| 103 |
"""
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
-
|
| 109 |
-
<
|
| 110 |
-
<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>
|
| 111 |
-
</div></div><div class="pills">
|
| 112 |
-
<span class="pill"><b>2–5</b> words per session</span>
|
| 113 |
-
<span class="pill"><b>1–3</b> examples per word</span>
|
| 114 |
-
<span class="pill">Session only — forgotten on close</span>
|
| 115 |
-
</div></div>"""
|
| 116 |
|
| 117 |
-
def
|
| 118 |
-
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
def add_example(name, audio, gain_choice, mem):
|
| 122 |
mem = dict(mem or {}); name = (name or "").strip().lower()
|
| 123 |
-
if not name:
|
| 124 |
-
|
|
|
|
|
|
|
| 125 |
R.gain = gain_choice; sr, wav = audio
|
| 126 |
mem[name] = list(mem.get(name, [])) + [R.embedding(wav, sr).tolist()]
|
| 127 |
n = len(mem[name]); tip = " Add 1 or 2 more examples to make it more sure." if n < 3 else ""
|
| 128 |
-
return mem,
|
| 129 |
|
| 130 |
-
def forget_all():
|
|
|
|
| 131 |
|
| 132 |
def recognize(audio, gain_choice, mem):
|
| 133 |
-
if not mem or len(mem) < 2:
|
| 134 |
-
|
|
|
|
|
|
|
| 135 |
R.gain = gain_choice; sr, wav = audio; q = R.embedding(wav, sr)
|
| 136 |
names = list(mem); P = torch.nn.functional.normalize(torch.stack([torch.tensor(mem[w]).mean(0) for w in names]), dim=-1)
|
| 137 |
sim = (P @ q).numpy(); p = np.exp(20 * (sim - sim.max())); p = p / p.sum()
|
| 138 |
i = int(sim.argmax()); j = np.argsort(-sim)[1] if len(sim) > 1 else i
|
| 139 |
gap = sim[i] - sim[j]
|
| 140 |
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.")
|
| 141 |
-
return
|
| 142 |
|
| 143 |
with gr.Blocks(title="KEEP Learn", css=CSS) as demo:
|
| 144 |
-
gr.HTML(
|
| 145 |
-
gr.
|
| 146 |
-
"**2.** Below, say one of the learned words again: KEEP tells you which one it recognizes.")
|
| 147 |
-
gain_radio = gr.Radio(["peak", "none"], value=cfg.get("gain", "peak"), label="Volume setting (peak = automatic, none = raw)")
|
| 148 |
mem = gr.State({})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
with gr.Row():
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
gr.Markdown("
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
|
|
|
| 163 |
gr.Markdown("_Experimental: what it learns here is forgotten when you close the page. "
|
| 164 |
"It works best with the same voice as the examples, and 2 to 3 examples per word._",
|
| 165 |
-
elem_classes=["
|
| 166 |
gr.Markdown("**Limits.** It recognizes word sounds, it does not understand speech. A noisy microphone or words that sound very alike can lower accuracy.",
|
| 167 |
-
elem_classes=["
|
| 168 |
demo.launch()
|
|
|
|
| 1 |
+
import os, json, sys, html, numpy as np, gradio as gr, torch
|
| 2 |
from huggingface_hub import snapshot_download
|
| 3 |
|
| 4 |
PRIVATE = os.environ.get("MODEL_REPO", "")
|
|
|
|
| 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 |
+
# Voice-first shell, same language as KEEP Listen: pure black, one narrow column,
|
| 12 |
+
# numbered steps instead of cards, huge type, pill actions.
|
| 13 |
CSS = """
|
| 14 |
+
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&display=swap');
|
| 15 |
+
html, body { background: #000000 !important; }
|
| 16 |
.gradio-container {
|
| 17 |
+
--body-background-fill: #000000;
|
| 18 |
+
--background-fill-primary: #000000;
|
| 19 |
+
--background-fill-secondary: #000000;
|
| 20 |
+
--block-background-fill: transparent;
|
| 21 |
+
--block-border-color: transparent;
|
| 22 |
+
--block-label-background-fill: transparent;
|
| 23 |
+
--block-label-text-color: #8A8A93;
|
| 24 |
+
--block-title-text-color: #F5F5F7;
|
| 25 |
+
--body-text-color: #F5F5F7;
|
| 26 |
+
--body-text-color-subdued: #8A8A93;
|
| 27 |
+
--button-primary-background-fill: #F5F5F7;
|
| 28 |
--button-primary-background-fill-hover: #FFFFFF;
|
| 29 |
+
--button-primary-text-color: #000000;
|
| 30 |
+
--button-secondary-background-fill: transparent;
|
| 31 |
+
--button-secondary-background-fill-hover: #1A1A1E;
|
| 32 |
+
--button-secondary-text-color: #F5F5F7;
|
| 33 |
+
--button-secondary-border-color: rgba(255,255,255,.22);
|
| 34 |
+
--input-background-fill: transparent;
|
| 35 |
+
--input-border-color: rgba(255,255,255,.22);
|
| 36 |
+
--input-text-color: #F5F5F7;
|
| 37 |
+
--input-placeholder-color: #6E6E73;
|
| 38 |
+
max-width: 560px !important; margin: 0 auto !important; padding: 26px 22px 44px !important;
|
| 39 |
+
background: #000000 !important; color: #F5F5F7 !important;
|
| 40 |
+
font-family: 'Space Grotesk', system-ui, sans-serif !important;
|
| 41 |
}
|
| 42 |
+
.gradio-container .block, .gradio-container .form, .gradio-container .wrap,
|
| 43 |
+
.gradio-container .panel, .gradio-container .component, .gradio-container .audio,
|
| 44 |
+
.gradio-container .plot, .gradio-container .input, .gradio-container .output {
|
| 45 |
+
background: transparent !important; border: none !important;
|
| 46 |
+
box-shadow: none !important; border-radius: 0 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
}
|
| 48 |
+
.gradio-container label span {
|
| 49 |
+
color: #8A8A93 !important; font-size: 11px !important; font-weight: 500 !important;
|
| 50 |
+
letter-spacing: 2.5px !important; text-transform: uppercase !important;
|
|
|
|
|
|
|
|
|
|
| 51 |
}
|
| 52 |
+
.gradio-container .prose { color: #C7C7CC !important; }
|
| 53 |
+
.gradio-container .prose strong { color: #F5F5F7 !important; }
|
| 54 |
+
.topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
| 55 |
+
.brand { font-weight: 700; font-size: 19px; letter-spacing: -.5px; }
|
| 56 |
+
.brand i { color: #E5C15D; font-style: normal; }
|
| 57 |
+
.ondevice { font-size: 12px; color: #8A8A93; border: 1px solid rgba(255,255,255,.18); border-radius: 999px; padding: 5px 12px; }
|
| 58 |
+
.intro h1 { font-size: 46px; font-weight: 700; letter-spacing: -2px; line-height: 1.04; margin: 30px 0 14px; }
|
| 59 |
+
.intro .sub { color: #8A8A93; font-size: 16px; line-height: 1.6; margin: 0; }
|
| 60 |
+
.intro .sub b { color: #C7C7CC; }
|
| 61 |
+
.step { display: flex; align-items: baseline; gap: 14px; margin: 36px 0 4px; }
|
| 62 |
+
.step .num { color: #E5C15D; font-weight: 700; font-size: 14px; letter-spacing: 1px; }
|
| 63 |
+
.step h2 { font-size: 27px; font-weight: 700; letter-spacing: -.5px; margin: 0; }
|
| 64 |
+
.hairline { height: 1px; background: rgba(255,255,255,.10); margin: 32px 0; }
|
| 65 |
+
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
|
| 66 |
+
.chip { font-size: 14px; color: #F5F5F7; background: #141416; border-radius: 999px; padding: 9px 15px; }
|
| 67 |
+
.chip span { color: #8A8A93; }
|
| 68 |
+
.dots { display: flex; gap: 9px; align-items: center; margin: 14px 0 4px; }
|
| 69 |
+
.dots i { width: 11px; height: 11px; border-radius: 50%; background: #26262A; }
|
| 70 |
+
.dots i.on { background: #E5C15D; }
|
| 71 |
+
.dots span { color: #8A8A93; font-size: 13px; margin-left: 8px; }
|
| 72 |
+
.qstage { text-align: center; padding: 10px 0 4px; }
|
| 73 |
+
.qstage .eyebrow { font-size: 11px; letter-spacing: 3px; text-transform: uppercase; color: #8A8A93; margin-bottom: 12px; }
|
| 74 |
+
.qstage .word { font-size: 64px; font-weight: 700; letter-spacing: -2.5px; line-height: 1; margin: 0; }
|
| 75 |
+
.qstage .word.dim { color: #333338; }
|
| 76 |
+
.qstage .bars { margin-top: 20px; display: flex; flex-direction: column; gap: 8px; }
|
| 77 |
+
.qstage .bars .row { display: flex; align-items: center; gap: 12px; font-size: 14px; color: #C7C7CC; }
|
| 78 |
+
.qstage .bars .row b { width: 110px; text-align: right; font-weight: 500; color: #F5F5F7; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
| 79 |
+
.qstage .bars .bar { flex: 1; height: 5px; background: #1C1C1F; border-radius: 99px; overflow: hidden; }
|
| 80 |
+
.qstage .bars .bar i { display: block; height: 100%; background: linear-gradient(90deg, #C9A227, #F5D98B); border-radius: 99px; }
|
| 81 |
+
.qstage .bars .pct { width: 46px; text-align: left; color: #8A8A93; }
|
| 82 |
+
.quiet { color: #8A8A93 !important; font-size: 13.5px !important; line-height: 1.65 !important; }
|
| 83 |
+
.quiet strong { color: #C7C7CC !important; }
|
| 84 |
+
.gradio-container button.primary { border-radius: 999px !important; height: 56px !important; font-size: 17px !important; font-weight: 700 !important; }
|
| 85 |
+
.gradio-container button:not(.primary) { border-radius: 999px !important; }
|
| 86 |
+
.gradio-container .audio button { border-radius: 999px !important; background: #141416 !important; border: 1px solid rgba(255,255,255,.14) !important; }
|
| 87 |
+
.gradio-container input[type="text"] {
|
| 88 |
+
background: transparent !important; border: none !important;
|
| 89 |
+
border-bottom: 2px solid rgba(255,255,255,.25) !important; border-radius: 0 !important;
|
| 90 |
+
font-size: 30px !important; font-weight: 700 !important; padding: 10px 0 !important; letter-spacing: -.5px !important;
|
| 91 |
}
|
| 92 |
+
.gradio-container input[type="text"]:focus { border-bottom-color: #E5C15D !important; box-shadow: none !important; outline: none !important; }
|
| 93 |
+
/* Gradio 6 renders Textbox as a textarea inside label.container.show_textbox_border */
|
| 94 |
+
.gradio-container label.container.show_textbox_border { border: none !important; background: transparent !important; box-shadow: none !important; }
|
| 95 |
+
.gradio-container .input-container { background: transparent !important; border: none !important; }
|
| 96 |
+
.gradio-container textarea {
|
| 97 |
+
background: transparent !important; border: none !important;
|
| 98 |
+
border-bottom: 2px solid rgba(255,255,255,.25) !important; border-radius: 0 !important;
|
| 99 |
+
font-size: 30px !important; font-weight: 700 !important; padding: 10px 0 !important; letter-spacing: -.5px !important;
|
| 100 |
+
color: #F5F5F7 !important;
|
| 101 |
}
|
| 102 |
+
.gradio-container textarea:focus { border-bottom-color: #E5C15D !important; box-shadow: none !important; outline: none !important; }
|
| 103 |
+
.gradio-container label[data-testid$="-radio-label"] {
|
| 104 |
+
border-radius: 999px !important; border: 1px solid rgba(255,255,255,.14) !important;
|
| 105 |
+
padding: 7px 15px !important; background: transparent !important; color: #8A8A93 !important;
|
| 106 |
}
|
| 107 |
+
.gradio-container label[data-testid$="-radio-label"].selected { border-color: #E5C15D !important; color: #F5F5F7 !important; }
|
| 108 |
+
.gradio-container label[data-testid$="-radio-label"] span { color: inherit !important; background: transparent !important; letter-spacing: 0 !important; text-transform: none !important; font-size: 14px !important; }
|
| 109 |
+
.footer { opacity: .45; }
|
| 110 |
"""
|
| 111 |
|
| 112 |
+
TOPBAR = """<div class="topbar"><div class="brand">KEEP <i>•</i> Learn</div><div class="ondevice">On-device · CPU</div></div>"""
|
| 113 |
+
INTRO = """<div class="intro"><h1>Teach KEEP<br>a new word.</h1><p class="sub">No retraining. It keeps the <b>fingerprint</b> of your examples, then recognizes by resemblance. What you teach here lives only for this session.</p></div>"""
|
| 114 |
+
|
| 115 |
+
def learned_html(mem):
|
| 116 |
+
if not mem:
|
| 117 |
+
return '<p class="quiet" style="margin:8px 0 0">No word learned yet.</p>'
|
| 118 |
+
chips = "".join(
|
| 119 |
+
f'<span class="chip">{html.escape(w)} <span>×{len(v)}</span></span>' for w, v in mem.items())
|
| 120 |
+
return f'<div class="chips">{chips}</div>'
|
| 121 |
+
|
| 122 |
+
def dots_html(mem, name):
|
| 123 |
+
n = len((mem or {}).get((name or "").strip().lower(), []))
|
| 124 |
+
dots = "".join('<i class="on"></i>' if i < min(n, 3) else '<i></i>' for i in range(3))
|
| 125 |
+
label = "example" if n == 1 else "examples"
|
| 126 |
+
return f'<div class="dots">{dots}<span>{n} {label} for this word</span></div>'
|
| 127 |
|
| 128 |
+
def qstage_empty():
|
| 129 |
+
return """<div class="qstage"><div class="eyebrow">Test it</div><p class="word dim">Say it back</p></div>"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
+
def qstage_result(names, probs):
|
| 132 |
+
rows = "".join(
|
| 133 |
+
f'<div class="row"><b>{html.escape(w)}</b><span class="bar"><i style="width:{p * 100:.0f}%"></i></span><span class="pct">{p:.0%}</span></div>'
|
| 134 |
+
for w, p in zip(names, probs))
|
| 135 |
+
return (f"""<div class="qstage"><div class="eyebrow">KEEP thinks you said</div><p class="word">{html.escape(names[int(np.argmax(probs))])}</p>"""
|
| 136 |
+
f"""<div class="bars">{rows}</div></div>""")
|
| 137 |
|
| 138 |
def add_example(name, audio, gain_choice, mem):
|
| 139 |
mem = dict(mem or {}); name = (name or "").strip().lower()
|
| 140 |
+
if not name:
|
| 141 |
+
return mem, learned_html(mem), dots_html(mem, name), "Type the word name first.", gr.update()
|
| 142 |
+
if audio is None:
|
| 143 |
+
return mem, learned_html(mem), dots_html(mem, name), "Record the word first.", gr.update()
|
| 144 |
R.gain = gain_choice; sr, wav = audio
|
| 145 |
mem[name] = list(mem.get(name, [])) + [R.embedding(wav, sr).tolist()]
|
| 146 |
n = len(mem[name]); tip = " Add 1 or 2 more examples to make it more sure." if n < 3 else ""
|
| 147 |
+
return mem, learned_html(mem), dots_html(mem, name), f"Example no. {n} of “{name}” memorized.{tip}", None
|
| 148 |
|
| 149 |
+
def forget_all():
|
| 150 |
+
return {}, learned_html({}), dots_html({}, ""), "Everything forgotten.", None
|
| 151 |
|
| 152 |
def recognize(audio, gain_choice, mem):
|
| 153 |
+
if not mem or len(mem) < 2:
|
| 154 |
+
return qstage_empty(), "Teach it at least **2 words** first (steps above)."
|
| 155 |
+
if audio is None:
|
| 156 |
+
return qstage_empty(), "Say one of the learned words."
|
| 157 |
R.gain = gain_choice; sr, wav = audio; q = R.embedding(wav, sr)
|
| 158 |
names = list(mem); P = torch.nn.functional.normalize(torch.stack([torch.tensor(mem[w]).mean(0) for w in names]), dim=-1)
|
| 159 |
sim = (P @ q).numpy(); p = np.exp(20 * (sim - sim.max())); p = p / p.sum()
|
| 160 |
i = int(sim.argmax()); j = np.argsort(-sim)[1] if len(sim) > 1 else i
|
| 161 |
gap = sim[i] - sim[j]
|
| 162 |
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.")
|
| 163 |
+
return qstage_result(names, p), msg
|
| 164 |
|
| 165 |
with gr.Blocks(title="KEEP Learn", css=CSS) as demo:
|
| 166 |
+
gr.HTML(TOPBAR)
|
| 167 |
+
gr.HTML(INTRO)
|
|
|
|
|
|
|
| 168 |
mem = gr.State({})
|
| 169 |
+
gr.HTML('<div class="step"><span class="num">01</span><h2>Name it</h2></div>')
|
| 170 |
+
name = gr.Textbox(label="Word name", placeholder="e.g. bonjour", show_label=False)
|
| 171 |
+
gr.HTML('<div class="step"><span class="num">02</span><h2>Say it</h2></div>')
|
| 172 |
+
ex_audio = gr.Audio(sources=["microphone", "upload"], type="numpy", label="Say this word, 1 to 3 times")
|
| 173 |
+
dots = gr.HTML(dots_html({}, ""))
|
| 174 |
with gr.Row():
|
| 175 |
+
b_add = gr.Button("Memorize", variant="primary"); b_forget = gr.Button("Forget everything")
|
| 176 |
+
info = gr.Markdown(elem_classes=["quiet"])
|
| 177 |
+
learned = gr.HTML(learned_html({}))
|
| 178 |
+
gr.HTML('<div class="hairline"></div>')
|
| 179 |
+
gr.HTML('<div class="step"><span class="num">03</span><h2>Test it</h2></div>')
|
| 180 |
+
q_audio = gr.Audio(sources=["microphone", "upload"], type="numpy", label="Say one of the learned words again")
|
| 181 |
+
q_stage = gr.HTML(qstage_empty())
|
| 182 |
+
q_msg = gr.Markdown(elem_classes=["quiet"])
|
| 183 |
+
q_btn = gr.Button("What did I say?", variant="primary")
|
| 184 |
+
gain_radio = gr.Radio(["peak", "none"], value=cfg.get("gain", "peak"), label="Volume setting (peak = automatic, none = raw)")
|
| 185 |
+
b_add.click(add_example, [name, ex_audio, gain_radio, mem], [mem, learned, dots, info, ex_audio])
|
| 186 |
+
b_forget.click(forget_all, None, [mem, learned, dots, info, ex_audio])
|
| 187 |
+
q_btn.click(recognize, [q_audio, gain_radio, mem], [q_stage, q_msg])
|
| 188 |
+
gr.HTML('<div class="hairline"></div>')
|
| 189 |
gr.Markdown("_Experimental: what it learns here is forgotten when you close the page. "
|
| 190 |
"It works best with the same voice as the examples, and 2 to 3 examples per word._",
|
| 191 |
+
elem_classes=["quiet"])
|
| 192 |
gr.Markdown("**Limits.** It recognizes word sounds, it does not understand speech. A noisy microphone or words that sound very alike can lower accuracy.",
|
| 193 |
+
elem_classes=["quiet"])
|
| 194 |
demo.launch()
|