feat(recipes): add location open and recipe ID copy to recipe modal

Add a de-emphasized meta footer to the recipe modal, mirroring the model
modal's hash footnote: a clickable file location on the left (opens the
recipe JSON via the generic open-file-location route, with the Docker
clipboard fallback) and a middle-truncated recipe ID with copy button on
the right.

The recipe detail API now exposes recipe_json_path so the frontend does
not have to guess the on-disk storage layout. Translations for the new
recipes.modal.* keys are filled in for all 9 locales, reusing the model
modal's openFileLocation wording per locale.
This commit is contained in:
Will Miao
2026-09-01 21:58:47 +08:00
parent 1fd7cc0123
commit 9584fa85c9
16 changed files with 665 additions and 2 deletions
+28
View File
@@ -2246,3 +2246,31 @@ async def test_reimport_without_any_source_returns_400(
assert payload["success"] is False
assert harness.analysis.local_calls == []
assert harness.persistence.delete_calls == []
async def test_get_recipe_detail_includes_recipe_json_path(
monkeypatch, tmp_path: Path
) -> None:
"""The detail response exposes the recipe JSON path for open-location UI."""
async with recipe_harness(monkeypatch, tmp_path) as harness:
recipes_dir = Path(harness.scanner.recipes_dir)
recipes_dir.mkdir(parents=True, exist_ok=True)
harness.scanner.recipes["recipe-1"] = {
"id": "recipe-1",
"title": "Demo",
"file_path": str(recipes_dir / "recipe-1.png"),
}
json_file = recipes_dir / "recipe-1.recipe.json"
json_file.write_text("{}", encoding="utf-8")
response = await harness.client.get("/api/lm/recipe/recipe-1")
assert response.status == 200
payload = await response.json()
assert payload["recipe_json_path"] == str(json_file)
# Without the JSON file on disk the key is omitted entirely.
json_file.unlink()
response = await harness.client.get("/api/lm/recipe/recipe-1")
assert response.status == 200
payload = await response.json()
assert "recipe_json_path" not in payload