Timo commited on
Commit
b74e7a1
·
1 Parent(s): 5f973c8
Files changed (2) hide show
  1. requirements.txt +5 -3
  2. src/streamlit_app.py +136 -40
requirements.txt CHANGED
@@ -1,3 +1,5 @@
1
- altair
2
- pandas
3
- streamlit
 
 
 
1
+ streamlit>=1.28
2
+ requests
3
+ huggingface_hub>=0.23 # if you pull the model from the Hub
4
+ joblib # if you pickle weights
5
+ scrython
src/streamlit_app.py CHANGED
@@ -1,40 +1,136 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
- import streamlit as st
5
-
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MTG Draft Assistant Streamlit App
3
+ ---------------------------------
4
+ A simple demo streamlit app that wraps a Magic: The Gathering drafting helper model
5
+ and can be deployed on Hugging Face Spaces (Streamlit SDK).
6
+
7
+ Replace the stub functions with your real model logic and data sources. See
8
+ the accompanying README for build instructions.
9
+ """
10
+
11
+ import os
12
+ import random
13
+ from typing import List, Dict
14
+
15
+ import streamlit as st
16
+
17
+ # -----------------------------------------------------------------------------
18
+ # 1. Model loading
19
+ # -----------------------------------------------------------------------------
20
+
21
+ @st.cache_resource(show_spinner="Loading draft model…")
22
+ def load_model():
23
+ """Load and return your trained drafting model.
24
+
25
+ Adapt this stub to your own ML pipeline. For example, if you have pushed a
26
+ `draft_model.joblib` artifact to the Hub as `username/mtg-draft-model`, you
27
+ could:
28
+
29
+ from huggingface_hub import hf_hub_download
30
+ import joblib, os
31
+
32
+ local = hf_hub_download(
33
+ repo_id="username/mtg-draft-model",
34
+ filename="draft_model.joblib",
35
+ token=os.getenv("HF_TOKEN"), # optional for private repos
36
+ )
37
+ return joblib.load(local)
38
+ """
39
+ return None # fallback: no-op model for demo purposes
40
+
41
+ model = load_model()
42
+
43
+ # -----------------------------------------------------------------------------
44
+ # 2. Helper functions
45
+ # -----------------------------------------------------------------------------
46
+
47
+ def suggest_pick(pack: List[Dict], picks: List[Dict]) -> Dict:
48
+ """Return the card dict the model recommends from the current pack."""
49
+ if model is None:
50
+ return random.choice(pack)
51
+ # 🔄 Replace with your model's inference API
52
+ return model.predict(pack=pack, picks=picks)
53
+
54
+
55
+ def fetch_card_image(card_name: str) -> str:
56
+ """Return a medium‑sized card image URL from the Scryfall API."""
57
+ import requests
58
+
59
+ response = requests.get(
60
+ "https://api.scryfall.com/cards/named",
61
+ params={"exact": card_name, "format": "json"},
62
+ )
63
+ response.raise_for_status()
64
+ data = response.json()
65
+ # multi‑faced cards may not have `image_uris`; fall back to the first face
66
+ if "image_uris" in data:
67
+ return data["image_uris"]["normal"]
68
+ return data["card_faces"][0]["image_uris"]["normal"]
69
+
70
+
71
+ def generate_booster(set_code: str) -> List[Dict]:
72
+ """Very small demo booster generator using Scryfall search."""
73
+ import requests
74
+
75
+ url = (
76
+ f"https://api.scryfall.com/cards/search?q=set%3A{set_code}+is%3Abooster+unique%3Aprints"
77
+ )
78
+ cards: List[Dict] = []
79
+ while url:
80
+ r = requests.get(url)
81
+ r.raise_for_status()
82
+ payload = r.json()
83
+ cards.extend(payload["data"])
84
+ url = payload.get("next_page") if payload.get("has_more") else None
85
+ return random.sample(cards, 15)
86
+
87
+
88
+ # -----------------------------------------------------------------------------
89
+ # 3. Streamlit UI
90
+ # -----------------------------------------------------------------------------
91
+
92
+ st.set_page_config(
93
+ page_title="MTG Draft Assistant",
94
+ page_icon="🃏",
95
+ )
96
+
97
+ st.title("🃏 MTG Draft Assistant")
98
+ st.caption("A minimal booster‑draft helper that runs entirely client‑side.")
99
+
100
+ with st.sidebar:
101
+ st.header("Draft setup")
102
+ set_code = st.text_input("Set code (e.g. WOE, LCI, MKM)", value="WOE")
103
+ if st.button("Start new draft"):
104
+ st.session_state["pack"] = generate_booster(set_code)
105
+ st.session_state["picks"] = []
106
+
107
+ # Create session variables on first run
108
+ st.session_state.setdefault("pack", [])
109
+ st.session_state.setdefault("picks", [])
110
+
111
+ if not st.session_state["pack"]:
112
+ st.info("Choose **Start new draft** in the sidebar to open pack 1.")
113
+ st.stop()
114
+
115
+ pack = st.session_state["pack"]
116
+ picks = st.session_state["picks"]
117
+
118
+ st.subheader(f"Pack {len(picks)//15 + 1} — Pick {len(picks) % 15 + 1}")
119
+ suggested = suggest_pick(pack, picks)
120
+ st.success(f"**Model suggests:** {suggested['name']}")
121
+
122
+ # Display pack as a grid of card images
123
+ cols = st.columns(5)
124
+ for idx, card in enumerate(pack):
125
+ col = cols[idx % 5]
126
+ col.image(fetch_card_image(card["name"]), use_column_width=True)
127
+ if col.button(f"Pick {card['name']}", key=f"pick-{idx}"):
128
+ picks.append(card)
129
+ pack.remove(card)
130
+ if not pack: # end of pack ⇒ open a fresh booster
131
+ st.session_state["pack"] = generate_booster(set_code)
132
+ st.experimental_rerun()
133
+
134
+ with st.expander("Current picks", expanded=False):
135
+ st.write("\n".join([c["name"] for c in picks]))
136
+