Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, json, hashlib
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from typing import Dict, List, Tuple, Optional
|
| 4 |
+
import gradio as gr
|
| 5 |
+
|
| 6 |
+
DATA_PATH = os.environ.get("DATA_PATH", "skills_dataset.jsonl")
|
| 7 |
+
|
| 8 |
+
# -----------------------------
|
| 9 |
+
# Utilities
|
| 10 |
+
# -----------------------------
|
| 11 |
+
def _hash_id(name: str) -> str:
|
| 12 |
+
# stable, short-ish ID for generated skills
|
| 13 |
+
h = hashlib.md5(name.encode("utf-8")).hexdigest()[:8].upper()
|
| 14 |
+
return f"GEN-{h}"
|
| 15 |
+
|
| 16 |
+
def load_dataset(path: str) -> List[dict]:
|
| 17 |
+
p = Path(path)
|
| 18 |
+
if p.exists():
|
| 19 |
+
rows = []
|
| 20 |
+
with p.open("r", encoding="utf-8") as f:
|
| 21 |
+
for line in f:
|
| 22 |
+
line = line.strip()
|
| 23 |
+
if line:
|
| 24 |
+
rows.append(json.loads(line))
|
| 25 |
+
return rows
|
| 26 |
+
# Minimal fallback dataset if no file present
|
| 27 |
+
return [
|
| 28 |
+
{
|
| 29 |
+
"skill_id":"S001","name":"Slash","category":"Combat","element":"None","tier":1,
|
| 30 |
+
"description":"A quick melee cut.","base_power":30,"cost_mana":0,"cooldown_s":2,
|
| 31 |
+
"status_effects":[],"scales_with":["strength"],"prerequisites":[],
|
| 32 |
+
"unlock_methods":["starter"],"rarity":"common",
|
| 33 |
+
"evolves_to":[{"to_skill_id":"S101","condition":"Use Slash 200 times"}],
|
| 34 |
+
"combines_with":[]
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"skill_id":"S101","name":"Cleave","category":"Combat","element":"None","tier":2,
|
| 38 |
+
"description":"A wide, sweeping strike.","base_power":55,"cost_mana":0,"cooldown_s":4,
|
| 39 |
+
"status_effects":["bleed_small"],"scales_with":["strength"],"prerequisites":["S001"],
|
| 40 |
+
"unlock_methods":["use_mastery"],"rarity":"uncommon","evolves_to":[],"combines_with":[]
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"skill_id":"S002","name":"Fireball","category":"Magic","element":"Fire","tier":1,
|
| 44 |
+
"description":"Hurl a small fire orb.","base_power":40,"cost_mana":20,"cooldown_s":5,
|
| 45 |
+
"status_effects":["burn"],"scales_with":["intelligence"],"prerequisites":[],
|
| 46 |
+
"unlock_methods":["starter"],"rarity":"common","evolves_to":[],"combines_with":[]
|
| 47 |
+
}
|
| 48 |
+
]
|
| 49 |
+
|
| 50 |
+
def index_dataset(rows: List[dict]):
|
| 51 |
+
by_id = {r["skill_id"]: r for r in rows}
|
| 52 |
+
by_name = {}
|
| 53 |
+
for r in rows:
|
| 54 |
+
# disambiguate duplicate names by appending (ID)
|
| 55 |
+
key = r["name"]
|
| 56 |
+
if key in by_name:
|
| 57 |
+
key = f'{r["name"]} ({r["skill_id"]})'
|
| 58 |
+
by_name[key] = r["skill_id"]
|
| 59 |
+
# Build combo map from skill-> list of {with, result, rule}
|
| 60 |
+
combos = {}
|
| 61 |
+
for r in rows:
|
| 62 |
+
for c in r.get("combines_with", []):
|
| 63 |
+
combos.setdefault(r["skill_id"], []).append(c)
|
| 64 |
+
# Evolutions from skill_id -> list of {"to_skill_id":..., "condition":...}
|
| 65 |
+
evol = {}
|
| 66 |
+
for r in rows:
|
| 67 |
+
for e in r.get("evolves_to", []):
|
| 68 |
+
evol.setdefault(r["skill_id"], []).append(e)
|
| 69 |
+
return by_id, by_name, combos, evol
|
| 70 |
+
|
| 71 |
+
# Rule packs for element infusion (extend as you like)
|
| 72 |
+
ELEMENT_PACK = {
|
| 73 |
+
"Fire": {"status_add":["burn"], "power_mult":1.15, "cost_add":+4, "desc_suffix":"Ignites targets."},
|
| 74 |
+
"Ice": {"status_add":["slow"], "power_mult":1.10, "cost_add":+4, "desc_suffix":"Chills on hit."},
|
| 75 |
+
"Poison":{"status_add":["poison"], "power_mult":1.12, "cost_add":+3, "desc_suffix":"Applies toxin."},
|
| 76 |
+
"Lightning":{"status_add":["shock"], "power_mult":1.14, "cost_add":+5, "desc_suffix":"Electrifies foes."},
|
| 77 |
+
"Wind": {"status_add":["knockback"], "power_mult":1.08, "cost_add":+2, "desc_suffix":"Carries force."},
|
| 78 |
+
"Earth": {"status_add":["stagger"], "power_mult":1.12, "cost_add":+2, "desc_suffix":"Hits heavy."},
|
| 79 |
+
"Light": {"status_add":["blind_short"], "power_mult":1.10, "cost_add":+4, "desc_suffix":"Radiant flare."},
|
| 80 |
+
"Shadow":{"status_add":["invisible_short"],"power_mult":1.06,"cost_add":+3,"desc_suffix":"Cloaked strike."},
|
| 81 |
+
"Water": {"status_add":["soak"], "power_mult":1.08, "cost_add":+3, "desc_suffix":"Drenches."},
|
| 82 |
+
"Arcane":{"status_add":["silence_short"],"power_mult":1.10,"cost_add":+5, "desc_suffix":"Raw surge."},
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
def generate_infused_skill(base: dict, element: str) -> dict:
|
| 86 |
+
pack = ELEMENT_PACK[element]
|
| 87 |
+
name = f"{element} {base['name']}" if element not in (base.get("element") or "") else base["name"]
|
| 88 |
+
new = dict(base) # shallow copy
|
| 89 |
+
new["name"] = name
|
| 90 |
+
new["element"] = element if base.get("element", "None") in ("None", "", None) else f"{base['element']}/{element}"
|
| 91 |
+
new["tier"] = max(1, int(base.get("tier", 1))) # same tier
|
| 92 |
+
new["base_power"] = int(round(base.get("base_power", 0) * pack["power_mult"]))
|
| 93 |
+
new["cost_mana"] = int(base.get("cost_mana", 0) + pack["cost_add"])
|
| 94 |
+
new["cooldown_s"] = base.get("cooldown_s", 0) # keep
|
| 95 |
+
new_effects = list(dict.fromkeys((base.get("status_effects") or []) + pack["status_add"]))
|
| 96 |
+
new["status_effects"] = new_effects
|
| 97 |
+
new["description"] = (base.get("description") or "") + " " + pack["desc_suffix"]
|
| 98 |
+
new["rarity"] = base.get("rarity", "common")
|
| 99 |
+
new["skill_id"] = _hash_id(f"{name}|{new['element']}|{new['base_power']}")
|
| 100 |
+
new["prerequisites"] = list(base.get("prerequisites") or [])
|
| 101 |
+
new["unlock_methods"] = list(base.get("unlock_methods") or []) + [f"infusion:{element.lower()}"]
|
| 102 |
+
# Generated skills have no baked edges; they can still be combined via rules
|
| 103 |
+
new["evolves_to"] = []
|
| 104 |
+
new["combines_with"] = []
|
| 105 |
+
return new
|
| 106 |
+
|
| 107 |
+
def dataset_combo_result(current: dict, with_id: str, by_id: Dict[str,dict], dataset_combos: Dict[str, List[dict]]) -> Optional[dict]:
|
| 108 |
+
# If dataset has an explicit combo from current->with_id, use that result skill definition
|
| 109 |
+
for c in dataset_combos.get(current["skill_id"], []):
|
| 110 |
+
if c["with_skill_id"] == with_id:
|
| 111 |
+
res_id = c["result_skill_id"]
|
| 112 |
+
return by_id.get(res_id)
|
| 113 |
+
# also check reverse
|
| 114 |
+
for c in dataset_combos.get(with_id, []):
|
| 115 |
+
if c["with_skill_id"] == current["skill_id"]:
|
| 116 |
+
res_id = c["result_skill_id"]
|
| 117 |
+
return by_id.get(res_id)
|
| 118 |
+
return None
|
| 119 |
+
|
| 120 |
+
def rule_combo_generate(a: dict, b: dict) -> dict:
|
| 121 |
+
# Simple generative fallback: merge name pieces, elements, and effects
|
| 122 |
+
el_a = a.get("element","None")
|
| 123 |
+
el_b = b.get("element","None")
|
| 124 |
+
merged_el = "None"
|
| 125 |
+
if el_a == "None": merged_el = el_b
|
| 126 |
+
elif el_b == "None": merged_el = el_a
|
| 127 |
+
else: merged_el = f"{el_a}/{el_b}"
|
| 128 |
+
|
| 129 |
+
name = f"{a['name']} + {b['name']}"
|
| 130 |
+
desc = f"Fusion of {a['name']} and {b['name']}."
|
| 131 |
+
base_power = int(round(a.get("base_power",0)*0.6 + b.get("base_power",0)*0.6)) + 8
|
| 132 |
+
cost = int(round(a.get("cost_mana",0)*0.5 + b.get("cost_mana",0)*0.5))
|
| 133 |
+
cd = max(a.get("cooldown_s",0), b.get("cooldown_s",0))
|
| 134 |
+
effects = list(dict.fromkeys((a.get("status_effects") or []) + (b.get("status_effects") or [])))
|
| 135 |
+
|
| 136 |
+
res = {
|
| 137 |
+
"skill_id": _hash_id(f"{name}|{merged_el}|{base_power}"),
|
| 138 |
+
"name": name,
|
| 139 |
+
"category": a.get("category","Combat"),
|
| 140 |
+
"element": merged_el,
|
| 141 |
+
"tier": max(a.get("tier",1), b.get("tier",1)) + 1,
|
| 142 |
+
"description": desc,
|
| 143 |
+
"base_power": base_power,
|
| 144 |
+
"cost_mana": cost,
|
| 145 |
+
"cooldown_s": cd,
|
| 146 |
+
"status_effects": effects,
|
| 147 |
+
"scales_with": sorted(list(set((a.get("scales_with") or []) + (b.get("scales_with") or [])))),
|
| 148 |
+
"prerequisites": [a["skill_id"], b["skill_id"]],
|
| 149 |
+
"unlock_methods": ["fusion_discovery"],
|
| 150 |
+
"rarity": "rare",
|
| 151 |
+
"evolves_to": [],
|
| 152 |
+
"combines_with": []
|
| 153 |
+
}
|
| 154 |
+
return res
|
| 155 |
+
|
| 156 |
+
def dataset_evolve(current: dict, evol_map: Dict[str,List[dict]], by_id: Dict[str,dict]) -> Optional[dict]:
|
| 157 |
+
# For the Space prototype, we "assume" conditions are met and take the first evolution
|
| 158 |
+
candidates = evol_map.get(current["skill_id"], [])
|
| 159 |
+
if not candidates: return None
|
| 160 |
+
to_id = candidates[0]["to_skill_id"]
|
| 161 |
+
return by_id.get(to_id)
|
| 162 |
+
|
| 163 |
+
def summarize(skill: dict) -> str:
|
| 164 |
+
lines = [
|
| 165 |
+
f"**{skill['name']}** (`{skill['skill_id']}`)",
|
| 166 |
+
f"- Tier: {skill.get('tier','?')} | Category: {skill.get('category','?')} | Element: {skill.get('element','None')}",
|
| 167 |
+
f"- Power: {skill.get('base_power',0)} | Cost: {skill.get('cost_mana',0)} | Cooldown: {skill.get('cooldown_s',0)}s",
|
| 168 |
+
f"- Effects: {', '.join(skill.get('status_effects') or []) or '—'}",
|
| 169 |
+
f"- Scales With: {', '.join(skill.get('scales_with') or []) or '—'}",
|
| 170 |
+
f"- Rarity: {skill.get('rarity','?')}",
|
| 171 |
+
f"- Desc: {skill.get('description','')}"
|
| 172 |
+
]
|
| 173 |
+
return "\n".join(lines)
|
| 174 |
+
|
| 175 |
+
# -----------------------------
|
| 176 |
+
# Load / Index dataset
|
| 177 |
+
# -----------------------------
|
| 178 |
+
DATA_ROWS = load_dataset(DATA_PATH)
|
| 179 |
+
BY_ID, BY_NAME, COMBOS, EVOLS = index_dataset(DATA_ROWS)
|
| 180 |
+
START_SKILLS = sorted(BY_NAME.keys()) # for dropdown
|
| 181 |
+
ELEMENT_CHOICES = ["Fire","Ice","Poison","Lightning","Wind","Earth","Light","Shadow","Water","Arcane"]
|
| 182 |
+
|
| 183 |
+
# -----------------------------
|
| 184 |
+
# Gradio App State Machine
|
| 185 |
+
# -----------------------------
|
| 186 |
+
def new_session():
|
| 187 |
+
return {"history": [], "current": None}
|
| 188 |
+
|
| 189 |
+
def start_skill(select_name: str, state):
|
| 190 |
+
sid = BY_NAME[select_name]
|
| 191 |
+
base = BY_ID[sid]
|
| 192 |
+
state = state or new_session()
|
| 193 |
+
state["history"] = [("Start", base)]
|
| 194 |
+
state["current"] = base
|
| 195 |
+
return summarize(base), state, gr.update(choices=_combine_choices(state["current"]))
|
| 196 |
+
|
| 197 |
+
def _combine_choices(current: Optional[dict]) -> List[str]:
|
| 198 |
+
if not current: return []
|
| 199 |
+
# offer dataset combos and also all dataset skills as free-form for rule fusion
|
| 200 |
+
names = []
|
| 201 |
+
# dataset-defined combos:
|
| 202 |
+
for c in COMBOS.get(current["skill_id"], []):
|
| 203 |
+
with_id = c["with_skill_id"]
|
| 204 |
+
target = BY_ID.get(with_id)
|
| 205 |
+
if target:
|
| 206 |
+
label = f"{target['name']} ({with_id})"
|
| 207 |
+
names.append(label)
|
| 208 |
+
# also allow any skill in the dataset as a free-form partner (dedupe later)
|
| 209 |
+
for sid, row in BY_ID.items():
|
| 210 |
+
label = f"{row['name']} ({sid})"
|
| 211 |
+
names.append(label)
|
| 212 |
+
return sorted(list(dict.fromkeys(names)))
|
| 213 |
+
|
| 214 |
+
def apply_infusion(element: str, state):
|
| 215 |
+
state = state or new_session()
|
| 216 |
+
if not state["current"]:
|
| 217 |
+
return "Pick a start skill first.", state
|
| 218 |
+
infused = generate_infused_skill(state["current"], element)
|
| 219 |
+
state["history"].append((f"Infuse {element}", infused))
|
| 220 |
+
state["current"] = infused
|
| 221 |
+
return summarize(infused), state
|
| 222 |
+
|
| 223 |
+
def apply_combo(partner_label: str, state):
|
| 224 |
+
state = state or new_session()
|
| 225 |
+
if not state["current"]:
|
| 226 |
+
return "Pick a start skill first.", state, gr.update()
|
| 227 |
+
# partner label is like "Fireball (S002)"
|
| 228 |
+
if "(" in partner_label and partner_label.endswith(")"):
|
| 229 |
+
partner_id = partner_label.split("(")[-1][:-1]
|
| 230 |
+
else:
|
| 231 |
+
# fallback name lookup (rare)
|
| 232 |
+
partner_id = BY_NAME.get(partner_label, None)
|
| 233 |
+
if partner_id is None or partner_id not in BY_ID:
|
| 234 |
+
return "Invalid partner.", state, gr.update()
|
| 235 |
+
|
| 236 |
+
partner = BY_ID[partner_id]
|
| 237 |
+
res = dataset_combo_result(state["current"], partner_id, BY_ID, COMBOS)
|
| 238 |
+
if res is None:
|
| 239 |
+
res = rule_combo_generate(state["current"], partner)
|
| 240 |
+
|
| 241 |
+
state["history"].append((f"Combine with {partner['name']}", res))
|
| 242 |
+
state["current"] = res
|
| 243 |
+
return summarize(res), state, gr.update(choices=_combine_choices(state["current"]))
|
| 244 |
+
|
| 245 |
+
def apply_dataset_evolve(state):
|
| 246 |
+
state = state or new_session()
|
| 247 |
+
if not state["current"]:
|
| 248 |
+
return "Pick a start skill first.", state, gr.update()
|
| 249 |
+
evo = dataset_evolve(state["current"], EVOLS, BY_ID)
|
| 250 |
+
if evo is None:
|
| 251 |
+
return "No dataset evolution available for this skill.", state, gr.update()
|
| 252 |
+
state["history"].append(("Evolve (dataset)", evo))
|
| 253 |
+
state["current"] = evo
|
| 254 |
+
return summarize(evo), state, gr.update(choices=_combine_choices(state["current"]))
|
| 255 |
+
|
| 256 |
+
def render_history(state):
|
| 257 |
+
if not state or not state.get("history"):
|
| 258 |
+
return "—"
|
| 259 |
+
out = []
|
| 260 |
+
for step, s in state["history"]:
|
| 261 |
+
out.append(f"### {step}\n" + summarize(s))
|
| 262 |
+
return "\n\n".join(out)
|
| 263 |
+
|
| 264 |
+
def export_build(state):
|
| 265 |
+
if not state or not state.get("history"):
|
| 266 |
+
return json.dumps({"build":[]}, indent=2)
|
| 267 |
+
return json.dumps(
|
| 268 |
+
{"build":[{"step":step, "skill":skill} for step, skill in state["history"]]},
|
| 269 |
+
indent=2
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
# -----------------------------
|
| 273 |
+
# Gradio UI
|
| 274 |
+
# -----------------------------
|
| 275 |
+
with gr.Blocks(title="Skill Forge: Evolve & Combine") as demo:
|
| 276 |
+
gr.Markdown("# 🛠️ Skill Forge\nPick a base skill, infuse an element, evolve, and combine—step by step.")
|
| 277 |
+
|
| 278 |
+
with gr.Row():
|
| 279 |
+
with gr.Column():
|
| 280 |
+
start_dd = gr.Dropdown(choices=START_SKILLS, label="Start Skill", value=START_SKILLS[0] if START_SKILLS else None)
|
| 281 |
+
start_btn = gr.Button("Start Build", variant="primary")
|
| 282 |
+
|
| 283 |
+
infuse_dd = gr.Dropdown(choices=ELEMENT_CHOICES, label="Infuse Element")
|
| 284 |
+
infuse_btn = gr.Button("Apply Infusion")
|
| 285 |
+
|
| 286 |
+
combo_dd = gr.Dropdown(choices=[], label="Combine With (dataset or free-form)")
|
| 287 |
+
combo_btn = gr.Button("Combine")
|
| 288 |
+
|
| 289 |
+
evo_btn = gr.Button("Evolve (use dataset edge if available)")
|
| 290 |
+
export_btn = gr.Button("Export Build JSON")
|
| 291 |
+
|
| 292 |
+
with gr.Column():
|
| 293 |
+
current_md = gr.Markdown("Current Skill will appear here.")
|
| 294 |
+
history_md = gr.Markdown("—")
|
| 295 |
+
|
| 296 |
+
state = gr.State(new_session())
|
| 297 |
+
|
| 298 |
+
# Wire events
|
| 299 |
+
start_btn.click(start_skill, inputs=[start_dd, state], outputs=[current_md, state, combo_dd])
|
| 300 |
+
infuse_btn.click(apply_infusion, inputs=[infuse_dd, state], outputs=[current_md, state]).then(render_history, inputs=state, outputs=history_md)
|
| 301 |
+
combo_btn.click(apply_combo, inputs=[combo_dd, state], outputs=[current_md, state, combo_dd]).then(render_history, inputs=state, outputs=history_md)
|
| 302 |
+
evo_btn.click(apply_dataset_evolve, inputs=[state], outputs=[current_md, state, combo_dd]).then(render_history, inputs=state, outputs=history_md)
|
| 303 |
+
export_btn.click(export_build, inputs=[state], outputs=[gr.Code(label="Build JSON", language="json")])
|
| 304 |
+
|
| 305 |
+
if __name__ == "__main__":
|
| 306 |
+
demo.launch()
|