Spaces:
Sleeping
Sleeping
| from io import BytesIO | |
| from PIL import Image | |
| from art import ( | |
| DiffusersImageClient, | |
| best_torch_dtype, | |
| card_art_prompt, | |
| illustrate_card, | |
| illustrate_cards, | |
| image_data_uri, | |
| move_pipe_to_device, | |
| theme_style, | |
| ) | |
| from budget import Card | |
| from primitives import Effect | |
| class FakeArtClient: | |
| # Return a deterministic image URI for prompts. | |
| def create_art(self, prompt: str) -> str: | |
| return f"uri:{prompt}" | |
| class BrokenArtClient: | |
| # Raise like a failing image backend. | |
| def create_art(self, prompt: str) -> str: | |
| raise RuntimeError("no art") | |
| class FakePipe: | |
| # Capture pipeline calls and return a PIL image. | |
| def __init__(self) -> None: | |
| self.calls = [] | |
| self.device = "" | |
| def __call__(self, **kwargs): | |
| self.calls.append(kwargs) | |
| return type("Result", (), {"images": [Image.new("RGB", (2, 2), "red")]})() | |
| def to(self, device: str): | |
| self.device = device | |
| return self | |
| class FakeTorch: | |
| float16 = "f16" | |
| float32 = "f32" | |
| class cuda: | |
| # Report CUDA as unavailable. | |
| def is_available() -> bool: | |
| return False | |
| class backends: | |
| class mps: | |
| # Report MPS as available. | |
| def is_available() -> bool: | |
| return True | |
| # Verify images are encoded as browser-renderable PNG data URIs. | |
| def test_image_data_uri() -> None: | |
| uri = image_data_uri(Image.new("RGB", (1, 1), "blue")) | |
| assert uri.startswith("data:image/png;base64,") | |
| # Verify diffusers image client calls the pipeline and returns encoded art. | |
| def test_diffusers_image_client_create_art() -> None: | |
| pipe = FakePipe() | |
| client = DiffusersImageClient(pipe, steps=1, width=64, height=64) | |
| uri = client.create_art("dark spell") | |
| assert uri.startswith("data:image/png;base64,") | |
| assert pipe.calls[0]["prompt"] == "dark spell" | |
| assert pipe.calls[0]["num_inference_steps"] == 1 | |
| assert pipe.calls[0]["width"] == 64 | |
| assert "text" in pipe.calls[0]["negative_prompt"] | |
| # Verify dtype and device helpers prefer MPS when CUDA is unavailable. | |
| def test_torch_helpers() -> None: | |
| pipe = FakePipe() | |
| assert best_torch_dtype(FakeTorch) == "f16" | |
| assert move_pipe_to_device(pipe, FakeTorch) is pipe | |
| assert pipe.device == "mps" | |
| # Verify card prompts use model-authored art prompts first. | |
| def test_card_art_prompt() -> None: | |
| card = Card("Spark", 1, "fire", "wuxia", (Effect("deal", amount=2),), art_prompt="ember saint") | |
| assert "ember saint" in card_art_prompt(card) | |
| assert "no text" in card_art_prompt(card) | |
| fallback = Card("Spark", 1, "fire", "wuxia", (Effect("deal", amount=2),)) | |
| prompt = card_art_prompt(fallback) | |
| assert "Spark" in prompt | |
| assert "Deal 2 damage." in prompt | |
| assert "wide wuxia fantasy spell illustration" in prompt | |
| # Verify user worlds become concrete visual style clauses. | |
| def test_theme_style() -> None: | |
| assert "anime fantasy key visual" in theme_style("Anime") | |
| assert "dark fantasy spell illustration" in theme_style("dark fantasy") | |
| # Verify illustration attaches generated art without changing card rules. | |
| def test_illustrate_card() -> None: | |
| card = Card("Spark", 1, "fire", "wuxia", (Effect("deal", amount=2),), art_prompt="ember") | |
| illustrated = illustrate_card(FakeArtClient(), card) | |
| assert illustrated.art_uri.startswith("uri:wide wuxia fantasy spell illustration") | |
| assert illustrated.rules_text() == card.rules_text() | |
| assert illustrate_card(None, card) is card | |
| assert illustrate_card(BrokenArtClient(), card) is card | |
| # Verify illustration maps across packs. | |
| def test_illustrate_cards() -> None: | |
| cards = (Card("Spark", 1, "fire", "wuxia", (Effect("deal", amount=2),), art_prompt="ember"),) | |
| assert illustrate_cards(FakeArtClient(), cards)[0].art_uri.startswith("uri:wide wuxia") | |