from __future__ import annotations import html import json import sys import traceback from pathlib import Path import gradio as gr try: import spaces except Exception: # local dev outside HF Spaces class _SpacesShim: @staticmethod def GPU(*args, **kwargs): def deco(fn): return fn return deco spaces = _SpacesShim() ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from compose.merge import compose from compose.provenance import provenance_sentence from registry import lora_choices SAGE = "#5f6f52" CREAM = "#f6f1e8" TERRACOTTA = "#b7653c" def render_node(node: dict, depth: int = 0) -> str: margin = depth * 22 children = "".join(render_node(child, depth + 1) for child in node.get("children", [])) status = node.get("status", "unknown") checkpoint = node.get("checkpoint_step") or "not selected yet" cycle = "
Cycle detected — recursion stopped.
" if node.get("cycle_detected") else "" return f"""
{html.escape(node.get('cultural_source') or node['lora_id'])} {node['weight_pct']:g}%
{html.escape(node['lora_id'])} · {html.escape(str(node.get('type')))} · {html.escape(str(status))}
checkpoint: {html.escape(str(checkpoint))}
{html.escape(node.get('hf_repo') or '')} {cycle}
{children} """ def render_lineage(provenance: dict) -> str: ancestry = provenance.get("ancestry", []) bars = [] colors = [SAGE, TERRACOTTA, "#3f5f8f", "#9a7b33", "#72517e"] for i, node in enumerate(ancestry): bars.append( f"
{node['weight_pct']:g}%
" ) cards = "".join(render_node(node) for node in ancestry) raw = html.escape(json.dumps(provenance, indent=2)) sentence = html.escape(provenance_sentence(provenance)) full_prompt = html.escape(provenance.get("full_prompt", "")) model = html.escape(provenance.get("inference_model", provenance.get("base_model", ""))) return f"""

Visual lineage

{sentence}

Model: {model}
Full prompt: {full_prompt}
{''.join(bars)}
{cards}
Raw provenance JSON
{raw}
""" @spaces.GPU(duration=180) def generate(lora_a: str, lora_b: str, blend_a: int, prompt: str, seed: int, size: int): if not prompt.strip(): raise gr.Error("Give the image a prompt first.") weight_a = blend_a / 100 weight_b = 1 - weight_a try: result = compose( lora_ids=[lora_a, lora_b], weights=[weight_a, weight_b], prompt=prompt, seed=int(seed), registry_path=str(ROOT / "registry/loras.json"), output_dir=str(ROOT / "outputs"), width=int(size), height=int(size), ) return result["image"], render_lineage(result["provenance"]) except Exception as e: traceback.print_exc() raise gr.Error(f"Generation failed: {type(e).__name__}: {e}") def build_demo(): choices = lora_choices(ROOT / "registry/loras.json") default_a = "eritrean_krar_v1" if any(v == "eritrean_krar_v1" for _, v in choices) else choices[0][1] default_b = default_a with gr.Blocks(title="Visual Lineage", theme=gr.themes.Soft(primary_hue="green")) as demo: gr.Markdown( "# Visual Lineage\n" "*A 23andMe for imagined instruments.*\n\n" "Live FLUX.2 [klein] inference with published instrument LoRAs. " "Select any two from Eritrean krar, Korean gayageum, or Brazilian berimbau " "and trace the visual lineage of every generation." ) with gr.Row(): with gr.Column(scale=1): lora_a = gr.Dropdown(label="Instrument/source A", choices=choices, value=default_a) lora_b = gr.Dropdown(label="Instrument/source B", choices=choices, value=default_b) blend = gr.Slider(0, 100, value=100, step=1, label="Blend: A % / B %") prompt = gr.Textbox(label="Describe the imagined instrument image", value="a musician holding a newly invented bowl-shaped string instrument in a small warm room", lines=3) seed = gr.Number(label="Seed", value=42, precision=0) size = gr.Slider(512, 1024, value=768, step=256, label="Image size") btn = gr.Button("Generate with live LoRA", variant="primary") with gr.Column(scale=2): output_image = gr.Image(label="Generated image", type="pil") lineage_panel = gr.HTML(label="Visual lineage") btn.click(generate, [lora_a, lora_b, blend, prompt, seed, size], [output_image, lineage_panel], show_progress="full") return demo if __name__ == "__main__": build_demo().launch()