Spaces:
Running on Zero
Running on Zero
kwabs22 Claude Opus 5.5 commited on
Commit ·
bbe0bad
1
Parent(s): 065c4f9
Harden tool inputs against blank and junk values
Browse filesSeeds are strings cast inside the tools (some MCP clients can't send
numbers); JSON arguments fall back to defaults when blank and name the
field when invalid; ledger tools say to translate first when empty.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
app.py
CHANGED
|
@@ -23,6 +23,26 @@ def _fail(e):
|
|
| 23 |
raise gr.Error(str(e))
|
| 24 |
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
@spaces.GPU(duration=60)
|
| 27 |
def generate_mesh(prompt):
|
| 28 |
"""GPU entry point for the generation tier (last in the fallback chain).
|
|
@@ -54,12 +74,12 @@ def check_words(sentence: str, primitives_json: str = "{}") -> str:
|
|
| 54 |
"primitive_shapes", "completions"}.
|
| 55 |
"""
|
| 56 |
try:
|
| 57 |
-
return json.dumps(_check_words(sentence,
|
| 58 |
except Exception as e:
|
| 59 |
_fail(e)
|
| 60 |
|
| 61 |
|
| 62 |
-
def translate_to_scene(sentence: str, primitives_json: str = "{}", seed:
|
| 63 |
"""Translate an English sentence into a 3D scene.
|
| 64 |
|
| 65 |
Produces the serial ledger (tokens in reading order, the items each word commits,
|
|
@@ -76,12 +96,12 @@ def translate_to_scene(sentence: str, primitives_json: str = "{}", seed: int = 0
|
|
| 76 |
(ledger JSON string, rendered PNG, GLB scene file).
|
| 77 |
"""
|
| 78 |
try:
|
| 79 |
-
return pipeline.translate(sentence,
|
| 80 |
except Exception as e:
|
| 81 |
_fail(e)
|
| 82 |
|
| 83 |
|
| 84 |
-
def refine_scene(ledger_json: str, changes_json: str = "[]", seed:
|
| 85 |
"""Refine a ledger: freeze some decisions and change the others, then re-render.
|
| 86 |
|
| 87 |
Changes are a JSON list of:
|
|
@@ -102,9 +122,9 @@ def refine_scene(ledger_json: str, changes_json: str = "[]", seed: int = 0) -> t
|
|
| 102 |
(ledger JSON string, rendered PNG, GLB scene file).
|
| 103 |
"""
|
| 104 |
try:
|
| 105 |
-
ledger =
|
| 106 |
-
pipeline.apply_changes(ledger,
|
| 107 |
-
return pipeline.outputs(ledger,
|
| 108 |
except Exception as e:
|
| 109 |
_fail(e)
|
| 110 |
|
|
@@ -124,7 +144,7 @@ def simplify_item(ledger_json: str, item_id: str, check_recognition: bool = Fals
|
|
| 124 |
(report JSON string, gallery of rung pictures, GLB file per rung).
|
| 125 |
"""
|
| 126 |
try:
|
| 127 |
-
ledger =
|
| 128 |
item = next((i for i in ledger["items"] if i["id"] == item_id), None)
|
| 129 |
if item is None or item["kind"] != "object":
|
| 130 |
raise ValueError(f"{item_id!r} is not an object item in this ledger")
|
|
@@ -152,7 +172,7 @@ def simplify_item(ledger_json: str, item_id: str, check_recognition: bool = Fals
|
|
| 152 |
_fail(e)
|
| 153 |
|
| 154 |
|
| 155 |
-
def describe_scene(ledger_json: str, seed:
|
| 156 |
"""Back-translate: render the ledger's scene and have a vision model describe it in English.
|
| 157 |
|
| 158 |
Comparing the description with the original sentence checks the translation.
|
|
@@ -165,9 +185,9 @@ def describe_scene(ledger_json: str, seed: int = 0) -> str:
|
|
| 165 |
JSON string: {"original", "back_translation", "nouns_recovered": {word: bool}}.
|
| 166 |
"""
|
| 167 |
try:
|
| 168 |
-
ledger =
|
| 169 |
meshes = pipeline.settle_all(ledger)
|
| 170 |
-
config = scene.configure(ledger, meshes,
|
| 171 |
png = scene.render_scene(ledger, meshes, config, os.path.join(tempfile.mkdtemp(), "scene.png"))
|
| 172 |
text = vlm.describe(png)
|
| 173 |
nouns = [i["word"] for i in ledger["items"] if i["kind"] == "object"]
|
|
@@ -206,7 +226,7 @@ def _complete(sentence, choice):
|
|
| 206 |
|
| 207 |
|
| 208 |
def _use_primitive(primitives_json, word, shape):
|
| 209 |
-
prims =
|
| 210 |
if word:
|
| 211 |
prims[word] = shape
|
| 212 |
return json.dumps(prims)
|
|
@@ -219,7 +239,7 @@ with gr.Blocks(title="VisualLearningIdea") as demo:
|
|
| 219 |
"verbs and prepositions are relations between meshes.")
|
| 220 |
with gr.Row():
|
| 221 |
sentence = gr.Textbox(label="Sentence", placeholder="the cat sat and the sun shone", scale=4)
|
| 222 |
-
seed = gr.
|
| 223 |
status = gr.Markdown()
|
| 224 |
suggest = gr.Radio(label="Suggestions", choices=[], interactive=True)
|
| 225 |
with gr.Row():
|
|
|
|
| 23 |
raise gr.Error(str(e))
|
| 24 |
|
| 25 |
|
| 26 |
+
def _json(text, name, default=None):
|
| 27 |
+
"""Parse a JSON argument; blank -> default, junk -> a readable error."""
|
| 28 |
+
if not (text or "").strip():
|
| 29 |
+
if default is None:
|
| 30 |
+
raise ValueError(f"{name} is empty; translate a sentence first")
|
| 31 |
+
return default
|
| 32 |
+
try:
|
| 33 |
+
return json.loads(text)
|
| 34 |
+
except json.JSONDecodeError:
|
| 35 |
+
raise ValueError(f"{name} is not valid JSON")
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _seed(value):
|
| 39 |
+
"""Seeds arrive as strings (some MCP clients can't send numbers)."""
|
| 40 |
+
try:
|
| 41 |
+
return int(float(str(value).strip() or 0))
|
| 42 |
+
except ValueError:
|
| 43 |
+
raise ValueError(f"seed must be a whole number, got {value!r}")
|
| 44 |
+
|
| 45 |
+
|
| 46 |
@spaces.GPU(duration=60)
|
| 47 |
def generate_mesh(prompt):
|
| 48 |
"""GPU entry point for the generation tier (last in the fallback chain).
|
|
|
|
| 74 |
"primitive_shapes", "completions"}.
|
| 75 |
"""
|
| 76 |
try:
|
| 77 |
+
return json.dumps(_check_words(sentence, _json(primitives_json, "primitives_json", {})))
|
| 78 |
except Exception as e:
|
| 79 |
_fail(e)
|
| 80 |
|
| 81 |
|
| 82 |
+
def translate_to_scene(sentence: str, primitives_json: str = "{}", seed: str = "0") -> tuple[str, str, str]:
|
| 83 |
"""Translate an English sentence into a 3D scene.
|
| 84 |
|
| 85 |
Produces the serial ledger (tokens in reading order, the items each word commits,
|
|
|
|
| 96 |
(ledger JSON string, rendered PNG, GLB scene file).
|
| 97 |
"""
|
| 98 |
try:
|
| 99 |
+
return pipeline.translate(sentence, _json(primitives_json, "primitives_json", {}), _seed(seed))
|
| 100 |
except Exception as e:
|
| 101 |
_fail(e)
|
| 102 |
|
| 103 |
|
| 104 |
+
def refine_scene(ledger_json: str, changes_json: str = "[]", seed: str = "0") -> tuple[str, str, str]:
|
| 105 |
"""Refine a ledger: freeze some decisions and change the others, then re-render.
|
| 106 |
|
| 107 |
Changes are a JSON list of:
|
|
|
|
| 122 |
(ledger JSON string, rendered PNG, GLB scene file).
|
| 123 |
"""
|
| 124 |
try:
|
| 125 |
+
ledger = _json(ledger_json, "ledger_json")
|
| 126 |
+
pipeline.apply_changes(ledger, _json(changes_json, "changes_json", []), _seed(seed))
|
| 127 |
+
return pipeline.outputs(ledger, _seed(seed))
|
| 128 |
except Exception as e:
|
| 129 |
_fail(e)
|
| 130 |
|
|
|
|
| 144 |
(report JSON string, gallery of rung pictures, GLB file per rung).
|
| 145 |
"""
|
| 146 |
try:
|
| 147 |
+
ledger = _json(ledger_json, "ledger_json")
|
| 148 |
item = next((i for i in ledger["items"] if i["id"] == item_id), None)
|
| 149 |
if item is None or item["kind"] != "object":
|
| 150 |
raise ValueError(f"{item_id!r} is not an object item in this ledger")
|
|
|
|
| 172 |
_fail(e)
|
| 173 |
|
| 174 |
|
| 175 |
+
def describe_scene(ledger_json: str, seed: str = "0") -> str:
|
| 176 |
"""Back-translate: render the ledger's scene and have a vision model describe it in English.
|
| 177 |
|
| 178 |
Comparing the description with the original sentence checks the translation.
|
|
|
|
| 185 |
JSON string: {"original", "back_translation", "nouns_recovered": {word: bool}}.
|
| 186 |
"""
|
| 187 |
try:
|
| 188 |
+
ledger = _json(ledger_json, "ledger_json")
|
| 189 |
meshes = pipeline.settle_all(ledger)
|
| 190 |
+
config = scene.configure(ledger, meshes, _seed(seed))
|
| 191 |
png = scene.render_scene(ledger, meshes, config, os.path.join(tempfile.mkdtemp(), "scene.png"))
|
| 192 |
text = vlm.describe(png)
|
| 193 |
nouns = [i["word"] for i in ledger["items"] if i["kind"] == "object"]
|
|
|
|
| 226 |
|
| 227 |
|
| 228 |
def _use_primitive(primitives_json, word, shape):
|
| 229 |
+
prims = _json(primitives_json, "primitives_json", {})
|
| 230 |
if word:
|
| 231 |
prims[word] = shape
|
| 232 |
return json.dumps(prims)
|
|
|
|
| 239 |
"verbs and prepositions are relations between meshes.")
|
| 240 |
with gr.Row():
|
| 241 |
sentence = gr.Textbox(label="Sentence", placeholder="the cat sat and the sun shone", scale=4)
|
| 242 |
+
seed = gr.Textbox(label="Configuration seed", value="0", scale=1)
|
| 243 |
status = gr.Markdown()
|
| 244 |
suggest = gr.Radio(label="Suggestions", choices=[], interactive=True)
|
| 245 |
with gr.Row():
|