Spaces:
Running on Zero
Fix card overflow + ghost translations of curated catalog answers
Browse files1. Drop the replacement inside <span class='ml-word'>: a non-breaking
space sequence forced lines to overflow narrow result cards and produced
the horizontal scroll seen during live generation. Plain spaces wrap
naturally under the existing `white-space: pre-wrap` rule.
2. CSS belt-and-braces on .ml-result-body β overflow-wrap: anywhere +
word-break: normal + max-width: 100%, plus overflow-x: hidden on
.ml-panel-body. Long latin binomena no longer push horizontal scroll.
3. do_translate / restore_original: drop the BY_FILENAME[filename] fallback.
Previously, when AI ANALYZE failed (quota / backend down) and last_answers
state was empty, translate silently fell back to the curated catalog
answer of the *picked* sample and translated THAT β producing a
confidently realistic translation that didn't match the live result on
screen. Now: empty state -> honest 'Run AI ANALYZE first' message and
hidden TRANSLATE button.
4. on_pick (sample switch) now also resets last_answers state to empty.
Without this, switching samples kept the previous image's live answer in
state, which could leak into translate/restore for an unrelated thumbnail.
|
@@ -1382,7 +1382,12 @@ footer { display: none !important; }
|
|
| 1382 |
font-size: 19px !important; font-weight: 800 !important;
|
| 1383 |
line-height: 1.45 !important; color: #ffffff !important;
|
| 1384 |
letter-spacing: -0.2px !important; white-space: pre-wrap;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1385 |
}
|
|
|
|
| 1386 |
.ml-caret::after { content: 'β'; color: #00DCE6; margin-left: 2px;
|
| 1387 |
animation: ml-blink 1s steps(2) infinite; font-weight: 400; }
|
| 1388 |
@keyframes ml-blink { 50% { opacity: 0; } }
|
|
@@ -1960,21 +1965,27 @@ with gr.Blocks(css=CSS, theme=gr.themes.Base(primary_hue="red", neutral_hue="zin
|
|
| 1960 |
except ValueError: grid = 0
|
| 1961 |
try: cross = int(cross_str or "0")
|
| 1962 |
except ValueError: cross = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1963 |
if not filename:
|
| 1964 |
return (folder_html(cat_label, None),
|
| 1965 |
viewport_html(None, shape, grid, cross), "", *empty_panels(),
|
| 1966 |
gr.Button(visible=False),
|
| 1967 |
-
gr.Dropdown(value=DEFAULT_LANG_DISPLAY)
|
|
|
|
| 1968 |
uri = full_uri(filename)
|
| 1969 |
return (folder_html(cat_label, filename),
|
| 1970 |
viewport_html(uri, shape, grid, cross), uri, *empty_panels(),
|
| 1971 |
gr.Button(visible=False),
|
| 1972 |
-
gr.Dropdown(value=DEFAULT_LANG_DISPLAY)
|
|
|
|
| 1973 |
|
| 1974 |
picked_filename.change(on_pick,
|
| 1975 |
[picked_filename, cat_state, shape_state, grid_state, cross_state],
|
| 1976 |
[folder_grid, viewport, viewport_uri, vanilla_panel, v2_panel, v3_panel,
|
| 1977 |
-
original_btn, lang_dropdown], api_name=False)
|
| 1978 |
|
| 1979 |
def on_file_upload(file_obj, shape, grid_str, cross_str):
|
| 1980 |
try: grid = int(grid_str or "0")
|
|
@@ -2092,7 +2103,11 @@ with gr.Blocks(css=CSS, theme=gr.themes.Base(primary_hue="red", neutral_hue="zin
|
|
| 2092 |
spans = []
|
| 2093 |
for i, tok in enumerate(tokens):
|
| 2094 |
delay = i * ms_per_word
|
| 2095 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2096 |
spans.append(
|
| 2097 |
f'<span class="ml-word" style="animation-delay:{delay}ms;">{safe}</span>'
|
| 2098 |
)
|
|
@@ -2171,20 +2186,19 @@ with gr.Blocks(css=CSS, theme=gr.themes.Base(primary_hue="red", neutral_hue="zin
|
|
| 2171 |
js=ANALYZE_PRE_JS, api_name=False)
|
| 2172 |
|
| 2173 |
def do_translate(filename, lang_label, answers):
|
| 2174 |
-
#
|
| 2175 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2176 |
sources = answers if (answers and any(answers.values())) else None
|
| 2177 |
if not sources:
|
| 2178 |
-
|
| 2179 |
-
|
| 2180 |
-
|
| 2181 |
-
|
| 2182 |
-
|
| 2183 |
-
panel_html("v3", msg, state="ready"),
|
| 2184 |
-
gr.Button(visible=False))
|
| 2185 |
-
sources = {"vanilla": s.get("vanilla_answer", ""),
|
| 2186 |
-
"v2": s.get("v2_answer", ""),
|
| 2187 |
-
"v3": s.get("v3_answer", "")}
|
| 2188 |
|
| 2189 |
lang_code = LANG_BY_DISPLAY.get(lang_label, "en")
|
| 2190 |
lang_name = next((name for _, name, code in LANGUAGES if code == lang_code), "English")
|
|
@@ -2240,20 +2254,15 @@ with gr.Blocks(css=CSS, theme=gr.themes.Base(primary_hue="red", neutral_hue="zin
|
|
| 2240 |
[vanilla_panel, v2_panel, v3_panel, original_btn], api_name=False)
|
| 2241 |
|
| 2242 |
def restore_original(filename, answers):
|
| 2243 |
-
#
|
|
|
|
|
|
|
|
|
|
| 2244 |
sources = answers if (answers and any(answers.values())) else None
|
| 2245 |
if not sources:
|
| 2246 |
-
|
| 2247 |
-
|
| 2248 |
-
|
| 2249 |
-
return (panel_html("vanilla", msg, state="ready"),
|
| 2250 |
-
panel_html("v2", msg, state="ready"),
|
| 2251 |
-
panel_html("v3", msg, state="ready"),
|
| 2252 |
-
gr.Button(visible=False),
|
| 2253 |
-
gr.Dropdown(value=DEFAULT_LANG_DISPLAY))
|
| 2254 |
-
sources = {"vanilla": s.get("vanilla_answer", ""),
|
| 2255 |
-
"v2": s.get("v2_answer", ""),
|
| 2256 |
-
"v3": s.get("v3_answer", "")}
|
| 2257 |
return (panel_html("vanilla", sources.get("vanilla", "")),
|
| 2258 |
panel_html("v2", sources.get("v2", "")),
|
| 2259 |
panel_html("v3", sources.get("v3", "")),
|
|
|
|
| 1382 |
font-size: 19px !important; font-weight: 800 !important;
|
| 1383 |
line-height: 1.45 !important; color: #ffffff !important;
|
| 1384 |
letter-spacing: -0.2px !important; white-space: pre-wrap;
|
| 1385 |
+
/* Force long latin binomena (e.g. *Cocconeis-placentula*) and code-like
|
| 1386 |
+
tokens to break inside the card instead of pushing a horizontal scroll. */
|
| 1387 |
+
overflow-wrap: anywhere; word-break: normal;
|
| 1388 |
+
max-width: 100%;
|
| 1389 |
}
|
| 1390 |
+
.ml-panel-body { overflow-x: hidden; }
|
| 1391 |
.ml-caret::after { content: 'β'; color: #00DCE6; margin-left: 2px;
|
| 1392 |
animation: ml-blink 1s steps(2) infinite; font-weight: 400; }
|
| 1393 |
@keyframes ml-blink { 50% { opacity: 0; } }
|
|
|
|
| 1965 |
except ValueError: grid = 0
|
| 1966 |
try: cross = int(cross_str or "0")
|
| 1967 |
except ValueError: cross = 0
|
| 1968 |
+
# Reset live-answer state on every sample switch β without this the
|
| 1969 |
+
# previous image's live answer could leak into translate/restore for
|
| 1970 |
+
# the next sample and look like a real result.
|
| 1971 |
+
cleared_state = {"vanilla": "", "v2": "", "v3": ""}
|
| 1972 |
if not filename:
|
| 1973 |
return (folder_html(cat_label, None),
|
| 1974 |
viewport_html(None, shape, grid, cross), "", *empty_panels(),
|
| 1975 |
gr.Button(visible=False),
|
| 1976 |
+
gr.Dropdown(value=DEFAULT_LANG_DISPLAY),
|
| 1977 |
+
cleared_state)
|
| 1978 |
uri = full_uri(filename)
|
| 1979 |
return (folder_html(cat_label, filename),
|
| 1980 |
viewport_html(uri, shape, grid, cross), uri, *empty_panels(),
|
| 1981 |
gr.Button(visible=False),
|
| 1982 |
+
gr.Dropdown(value=DEFAULT_LANG_DISPLAY),
|
| 1983 |
+
cleared_state)
|
| 1984 |
|
| 1985 |
picked_filename.change(on_pick,
|
| 1986 |
[picked_filename, cat_state, shape_state, grid_state, cross_state],
|
| 1987 |
[folder_grid, viewport, viewport_uri, vanilla_panel, v2_panel, v3_panel,
|
| 1988 |
+
original_btn, lang_dropdown, last_answers], api_name=False)
|
| 1989 |
|
| 1990 |
def on_file_upload(file_obj, shape, grid_str, cross_str):
|
| 1991 |
try: grid = int(grid_str or "0")
|
|
|
|
| 2103 |
spans = []
|
| 2104 |
for i, tok in enumerate(tokens):
|
| 2105 |
delay = i * ms_per_word
|
| 2106 |
+
# Plain HTML-escaped token; trailing whitespace is kept as a real
|
| 2107 |
+
# space so the parent's `white-space: pre-wrap` lets the browser
|
| 2108 |
+
# break lines naturally. Using here forces a non-breaking
|
| 2109 |
+
# run that overflows narrow cards and creates ugly h-scroll.
|
| 2110 |
+
safe = _html.escape(tok)
|
| 2111 |
spans.append(
|
| 2112 |
f'<span class="ml-word" style="animation-delay:{delay}ms;">{safe}</span>'
|
| 2113 |
)
|
|
|
|
| 2186 |
js=ANALYZE_PRE_JS, api_name=False)
|
| 2187 |
|
| 2188 |
def do_translate(filename, lang_label, answers):
|
| 2189 |
+
# Translate ONLY what the live model produced this session. The previous
|
| 2190 |
+
# version fell back to BY_FILENAME[filename] (curated catalog answers)
|
| 2191 |
+
# when state was empty β which silently translated the wrong image's
|
| 2192 |
+
# pre-baked text whenever AI ANALYZE failed (quota / backend down).
|
| 2193 |
+
# That looked like a successful translation of fictional content. Now:
|
| 2194 |
+
# no live answer β honest "Run AI ANALYZE first" message.
|
| 2195 |
sources = answers if (answers and any(answers.values())) else None
|
| 2196 |
if not sources:
|
| 2197 |
+
msg = "Run AI ANALYZE first to get an answer to translate."
|
| 2198 |
+
return (panel_html("vanilla", msg, state="ready"),
|
| 2199 |
+
panel_html("v2", msg, state="ready"),
|
| 2200 |
+
panel_html("v3", msg, state="ready"),
|
| 2201 |
+
gr.Button(visible=False))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2202 |
|
| 2203 |
lang_code = LANG_BY_DISPLAY.get(lang_label, "en")
|
| 2204 |
lang_name = next((name for _, name, code in LANGUAGES if code == lang_code), "English")
|
|
|
|
| 2254 |
[vanilla_panel, v2_panel, v3_panel, original_btn], api_name=False)
|
| 2255 |
|
| 2256 |
def restore_original(filename, answers):
|
| 2257 |
+
# Restore ONLY the live answer that produced this translation. Same
|
| 2258 |
+
# rationale as do_translate: never fall back to BY_FILENAME catalog β
|
| 2259 |
+
# otherwise pressing ORIGINAL after a failed analyze silently restores
|
| 2260 |
+
# a pre-baked answer for a different image.
|
| 2261 |
sources = answers if (answers and any(answers.values())) else None
|
| 2262 |
if not sources:
|
| 2263 |
+
return (*empty_panels(),
|
| 2264 |
+
gr.Button(visible=False),
|
| 2265 |
+
gr.Dropdown(value=DEFAULT_LANG_DISPLAY))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2266 |
return (panel_html("vanilla", sources.get("vanilla", "")),
|
| 2267 |
panel_html("v2", sources.get("v2", "")),
|
| 2268 |
panel_html("v3", sources.get("v3", "")),
|