fix: reserved key names, per-name shape consistency and prompt ids checked on import, Esc leaves a typed field first, a resumed wizard is an ordinary run
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
69f3019d9c
commit
43c29ced07
@@ -791,7 +791,13 @@
|
||||
// A layer open above the wizard (Settings, a floating window, the restart overlay) owns Esc.
|
||||
if (document.querySelector(".restart-overlay, .sw-overlay, .fw:not([hidden])")) return;
|
||||
const t = document.activeElement;
|
||||
if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT")) return;
|
||||
if (t && t.tagName === "SELECT") return;
|
||||
// Typed text is not thrown away by a stray Esc: the first one leaves the field, the next closes.
|
||||
if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA") && String(t.value || "").trim()) {
|
||||
e.preventDefault();
|
||||
t.blur();
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
leave();
|
||||
}
|
||||
@@ -843,6 +849,7 @@
|
||||
const saved = (resume || carry) ? restore() : null;
|
||||
if (saved && resume && saved.resumable) {
|
||||
Object.assign(ctx, saved.ctx || {});
|
||||
save(false); // resumed: from here an ordinary run, closable for good
|
||||
idx = Math.min(Math.max(saved.idx || 0, 0), steps.length - 1);
|
||||
// The project survived the restart; the in-memory handle did not.
|
||||
const pid = saved.ctx?.projectId;
|
||||
|
||||
@@ -46,7 +46,8 @@ MAX_ROWS = 200
|
||||
|
||||
|
||||
def valid(key) -> bool:
|
||||
return isinstance(key, str) and bool(_KEY.match(key)) and not key.endswith((".", " "))
|
||||
return isinstance(key, str) and bool(_KEY.match(key)) and not key.endswith((".", " ")) \
|
||||
and not key.lower().endswith(".json") # `latest.json` lives beside the keys: a key cannot share its name
|
||||
|
||||
|
||||
def _dir(key):
|
||||
@@ -165,6 +166,8 @@ def rate(prompt_id, rating, axis=None):
|
||||
`rating` None clears: the row is removed, nothing is learned from that clip.
|
||||
`axis` only goes with "disliked" (see AXES); any other rating drops it.
|
||||
"""
|
||||
if not isinstance(prompt_id, str) or not prompt_id:
|
||||
raise ValueError("a rating names the clip it is about (no prompt id was given)")
|
||||
if rating is not None and rating not in REWARD:
|
||||
raise ValueError(f"{rating!r} is not a rating")
|
||||
if axis is not None and axis not in AXES:
|
||||
@@ -293,14 +296,22 @@ def _sound_rows(data):
|
||||
the wrong shape would break every later rating."""
|
||||
if not isinstance(data, dict) or not isinstance(data.get("rows"), list):
|
||||
return False
|
||||
shapes = {}
|
||||
for row in data["rows"]:
|
||||
if not isinstance(row, dict) or isinstance(row.get("reward"), bool) or not isinstance(row.get("reward"), (int, float)):
|
||||
return False
|
||||
if not isinstance(row.get("prompt_id"), str) or not row["prompt_id"]:
|
||||
return False
|
||||
if row.get("axis") not in (None, *AXES):
|
||||
return False
|
||||
captured = row.get("rows")
|
||||
if not isinstance(captured, dict) or not all(isinstance(v, torch.Tensor) for v in captured.values()):
|
||||
return False
|
||||
# What is stacked later must agree: one name, one shape (a key trained across two model
|
||||
# families would otherwise abort every sampling that reads it).
|
||||
for name, v in captured.items():
|
||||
if shapes.setdefault(name, tuple(v.shape)) != tuple(v.shape):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -124,3 +124,15 @@ def test_a_damaged_zip_and_an_empty_key_are_sentences_not_500s(server): # noqa:
|
||||
status, out = _raw(server, "GET", "/funpack/api/m/taste/keys/fox/export")
|
||||
assert status == 400 and b"nothing to export" in out
|
||||
assert not store.valid("a ")
|
||||
|
||||
|
||||
def test_reserved_names_shapes_and_missing_prompt_ids_are_refused(server): # noqa: F811
|
||||
good = {"rows": [{"prompt_id": "p", "reward": 1.0, "rows": {"1": torch.ones(2)}}]}
|
||||
assert _raw(server, "POST", "/funpack/api/m/taste/keys/import?name=latest.json", _zip_of({"reins.pt": _torch_bytes(good)}))[0] == 400
|
||||
mixed = {"rows": [{"prompt_id": "p", "reward": 1.0, "rows": {"1": torch.ones(2)}},
|
||||
{"prompt_id": "q", "reward": -1.0, "rows": {"1": torch.ones(3)}}]}
|
||||
assert _raw(server, "POST", "/funpack/api/m/taste/keys/import?name=x", _zip_of({"reins.pt": _torch_bytes(mixed)}))[0] == 400
|
||||
nopid = {"rows": [{"reward": 1.0, "rows": {"1": torch.ones(2)}}]}
|
||||
assert _raw(server, "POST", "/funpack/api/m/taste/keys/import?name=x", _zip_of({"reins.pt": _torch_bytes(nopid)}))[0] == 400
|
||||
status, out = _request(server, "POST", "/funpack/api/m/taste/rate", {"rating": "liked"})
|
||||
assert status == 400
|
||||
|
||||
Reference in New Issue
Block a user