Simonlob commited on
Commit
5523412
·
1 Parent(s): 8ed1a69

add normalize_text

Browse files
Files changed (2) hide show
  1. gepard_inference/engine.py +36 -1
  2. index.html +8 -6
gepard_inference/engine.py CHANGED
@@ -10,7 +10,9 @@ the model is never reloaded between requests.
10
 
11
  from __future__ import annotations
12
 
 
13
  import os
 
14
  from dataclasses import dataclass, field
15
  from pathlib import Path
16
  from typing import Dict, List, NamedTuple, Optional, Tuple
@@ -22,6 +24,37 @@ import yaml
22
  from .runner import GepardRunner
23
  from .speakers import SpeakerLibrary
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  class Example(NamedTuple):
27
  """A demo row for the UI examples table.
@@ -295,7 +328,9 @@ class GepardEngine:
295
  """
296
  if self.runner is None:
297
  raise RuntimeError("GepardEngine.load() must be called before synthesize()")
298
- text = (text or "").strip()
 
 
299
  if not text:
300
  raise ValueError("text is empty")
301
 
 
10
 
11
  from __future__ import annotations
12
 
13
+ import html
14
  import os
15
+ import re
16
  from dataclasses import dataclass, field
17
  from pathlib import Path
18
  from typing import Dict, List, NamedTuple, Optional, Tuple
 
24
  from .runner import GepardRunner
25
  from .speakers import SpeakerLibrary
26
 
27
+ # Tag-like sequences only: "<" must be followed by a letter or "/". This spares
28
+ # legitimate prose such as "2 < 3" or "<3" while catching "<div>", "</span>",
29
+ # "<br/>". Applied AFTER html.unescape so escaped markup (&lt;script&gt;) is
30
+ # caught too.
31
+ _HTML_TAG_RE = re.compile(r"</?[A-Za-z][^>]*>")
32
+ # Zero-width / BOM characters that are not caught by \s.
33
+ _ZERO_WIDTH_RE = re.compile(r"[\u200B-\u200D\uFEFF]")
34
+ _WHITESPACE_RE = re.compile(r"\s+")
35
+
36
+
37
+ def normalize_text(text: Optional[str]) -> str:
38
+ """Clean arbitrary client text down to what the tokenizer expects.
39
+
40
+ ``/synthesize`` is a public API (browser JS, ``gradio_client``, curl), so
41
+ the input may carry markup or invisible characters the model never saw in
42
+ training. This is the single authoritative choke point right before the
43
+ text reaches the model.
44
+
45
+ Steps: decode HTML entities -> strip HTML-ish tags -> drop zero-width
46
+ chars -> collapse all whitespace (newlines, tabs, (narrow) nbsp) to single
47
+ spaces -> trim. ``\\s`` matches ``\\u00A0`` in Python's Unicode mode, so nbsp
48
+ is handled by the whitespace collapse.
49
+ """
50
+ if not text:
51
+ return ""
52
+ text = html.unescape(text) # &amp; &nbsp; &#39; -> chars
53
+ text = _HTML_TAG_RE.sub(" ", text) # <div>, </span>, <br/> -> space
54
+ text = _ZERO_WIDTH_RE.sub("", text) # zero-width chars -> drop
55
+ text = _WHITESPACE_RE.sub(" ", text) # newlines/tabs/nbsp/runs -> space
56
+ return text.strip()
57
+
58
 
59
  class Example(NamedTuple):
60
  """A demo row for the UI examples table.
 
328
  """
329
  if self.runner is None:
330
  raise RuntimeError("GepardEngine.load() must be called before synthesize()")
331
+ # Authoritative cleanup right before the model — protects every caller
332
+ # (browser JS, gradio_client, curl), not just the frontend.
333
+ text = normalize_text(text)
334
  if not text:
335
  raise ValueError("text is empty")
336
 
index.html CHANGED
@@ -828,12 +828,14 @@ let wordSpans = [];
828
  let hlRAF = null;
829
  let isPlaying = false;
830
 
 
 
831
  const sliderSpec = [
832
- { key: "temperature", label: "Temperature", fmt: v => Number(v).toFixed(2) },
833
- { key: "top_k", label: "Top-K", fmt: v => String(Math.round(v)) },
834
- { key: "max_frames", label: "Max Frames", fmt: v => String(Math.round(v)) },
835
- { key: "repetition_penalty", label: "Rep. Penalty", fmt: v => Number(v).toFixed(2) },
836
- { key: "repetition_window", label: "Rep. Window", fmt: v => String(Math.round(v)) },
837
  ];
838
 
839
  const pad = (n, w = 2) => String(Math.floor(n)).padStart(w, "0");
@@ -1018,7 +1020,7 @@ const buildSliders = () => {
1018
  const min = cfg.limits[`min_${spec.key}`];
1019
  const max = cfg.limits[`max_${spec.key}`];
1020
  const def = cfg.defaults[spec.key];
1021
- const step = spec.key === "temperature" || spec.key === "repetition_penalty" ? 0.01 : 1;
1022
 
1023
  // Slider row
1024
  const row = document.createElement("div"); row.className = "slider";
 
828
  let hlRAF = null;
829
  let isPlaying = false;
830
 
831
+ // step values mirror the original Blocks UI's validated grid — e.g. max_frames
832
+ // must stay on the 43-frame codec chunk; off-grid values degrade generation.
833
  const sliderSpec = [
834
+ { key: "temperature", label: "Temperature", step: 0.05, fmt: v => Number(v).toFixed(2) },
835
+ { key: "top_k", label: "Top-K", step: 1, fmt: v => String(Math.round(v)) },
836
+ { key: "max_frames", label: "Max Frames", step: 43, fmt: v => String(Math.round(v)) },
837
+ { key: "repetition_penalty", label: "Rep. Penalty", step: 0.01, fmt: v => Number(v).toFixed(2) },
838
+ { key: "repetition_window", label: "Rep. Window", step: 4, fmt: v => String(Math.round(v)) },
839
  ];
840
 
841
  const pad = (n, w = 2) => String(Math.floor(n)).padStart(w, "0");
 
1020
  const min = cfg.limits[`min_${spec.key}`];
1021
  const max = cfg.limits[`max_${spec.key}`];
1022
  const def = cfg.defaults[spec.key];
1023
+ const step = spec.step;
1024
 
1025
  // Slider row
1026
  const row = document.createElement("div"); row.className = "slider";