openfree commited on
Commit
d855b10
ยท
verified ยท
1 Parent(s): 5293a08

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +53 -68
app.py CHANGED
@@ -4,7 +4,7 @@ import re
4
  import gradio as gr
5
 
6
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. ๊ธฐ๋ณธ ์„ค์ • โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
7
- BEST_FILE, PER_PAGE = "best_games.json", 9 # โถ ํŽ˜์ด์ง€๋‹น 9๊ฐœ ์œ ์ง€
8
 
9
 
10
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 2. BEST ๋ฐ์ดํ„ฐ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -16,16 +16,13 @@ def _init_best():
16
  def _load_best():
17
  try:
18
  raw = json.load(open(BEST_FILE))
19
- # URL ๋ฆฌ์ŠคํŠธ๋งŒ ๋ฐ˜ํ™˜
20
- if isinstance(raw, list):
21
- return [u if isinstance(u, str) else u.get("url") for u in raw]
22
- return []
23
  except Exception as e:
24
  print("BEST ๋กœ๋“œ ์˜ค๋ฅ˜:", e)
25
  return []
26
 
27
 
28
- def _save_best(lst): # URLโ€†๋ฆฌ์ŠคํŠธ ์ €์žฅ
29
  try:
30
  json.dump(lst, open(BEST_FILE, "w"), ensure_ascii=False, indent=2)
31
  return True
@@ -34,11 +31,8 @@ def _save_best(lst): # URLโ€†๋ฆฌ์ŠคํŠธ ์ €์žฅ
34
  return False
35
 
36
 
 
37
  def to_hub_space_url(url: str) -> str:
38
- """
39
- *.hf.space URL์„ https://huggingface.co/spaces/<owner>/<space> ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜
40
- ๋‹ค๋ฅธ URL์€ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜
41
- """
42
  m = re.match(r"https?://([^-]+)-([^.]+)\.hf\.space(/.*)?", url)
43
  if m:
44
  owner, space, _ = m.groups()
@@ -47,7 +41,6 @@ def to_hub_space_url(url: str) -> str:
47
 
48
 
49
  def add_url_to_best(url: str):
50
- # ์ €์žฅํ•  ๋•Œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ค€ ์›๋ณธ์„ ๊ทธ๋Œ€๋กœ ๋‘”๋‹ค
51
  data = _load_best()
52
  if url in data:
53
  return False
@@ -63,72 +56,54 @@ def page(lst, pg):
63
 
64
 
65
  def process_url_for_iframe(url):
66
- """
67
- ๋ฐ˜ํ™˜: (iframe_url, extra_class, alternate_urls)
68
- extra_class : '' | 'huggingface' | 'hfspace'
69
- """
70
- # Hugging Face Spaces embed (Gradio/Streamlit)
71
  if "huggingface.co/spaces" in url:
72
  owner, name = url.rstrip("/").split("/spaces/")[1].split("/")[:2]
73
  return f"https://huggingface.co/spaces/{owner}/{name}/embed", "huggingface", []
74
 
75
- # *.hf.space (์ •์ /static Space ํฌํ•จ)
76
  m = re.match(r"https?://([^/]+)\.hf\.space(/.*)?", url)
77
  if m:
78
  sub, rest = m.groups()
79
  static_url = f"https://{sub}.static.hf.space{rest or ''}"
80
- # alt_urls ์— ์›๋ณธ ์ €์žฅ(์‹คํŒจ ์‹œ ์žฌ์‹œ๋„ ๊ฐ€๋Šฅ)
81
  return static_url, "hfspace", [url]
82
 
83
  return url, "", []
84
 
85
 
86
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 6. HTML ๊ทธ๋ฆฌ๋“œ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
87
  def html(cards, pg, total):
88
  if not cards:
89
- return (
90
- "<div style='text-align:center;padding:70px;color:#555;'>"
91
- "ํ‘œ์‹œํ•  ๋ฐฐํฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.</div>"
92
- )
93
 
94
  css = r"""
95
  <style>
96
- /* ํŒŒ์Šคํ…” ๊ทธ๋ผ๋””์—์ด์…˜ ๋ฐฐ๊ฒฝ */
97
  body{
98
  margin:0;padding:0;font-family:Poppins,sans-serif;
99
  background:linear-gradient(135deg,#fdf4ff 0%,#f6fbff 50%,#fffaf4 100%);
100
  background-attachment:fixed;
101
  overflow-x:hidden;overflow-y:auto;
102
  }
103
- .container{width:100%;padding:10px 10px 70px;box-sizing:border-box;}
104
  .grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:100%;}
105
  .card{
106
  background:#fff;border-radius:10px;overflow:hidden;box-shadow:0 4px 10px rgba(0,0,0,0.08);
107
  height:420px;display:flex;flex-direction:column;position:relative;
108
  }
109
- /* ๊ฒŒ์ž„ ํ™”๋ฉด ์ถ•์†Œ */
110
  .frame{flex:1;position:relative;overflow:hidden;}
111
  .frame iframe{
112
  position:absolute;top:0;left:0;
113
  width:166.667%;height:166.667%;
114
  transform:scale(0.6);transform-origin:top left;border:0;
115
  }
116
- /* Gradio/Streamlit Embed์šฉ */
117
- .frame.huggingface iframe{
118
- width:100%!important;height:100%!important;
119
- transform:none!important;border:none!important;
120
- }
121
- /* hf.space ์ „์ฒด ํŽ˜์ด์ง€์šฉ - ํ•œ ๋ฒˆ ๋” ์ถ•์†Œ */
122
  .frame.hfspace iframe{
123
  width:200%;height:200%;
124
- transform:scale(0.5);
125
- transform-origin:top left;border:0;
126
  }
127
- /* ํ•˜๋‹จ ๋ฐ”๋กœ๊ฐ€๊ธฐ */
128
  .foot{height:34px;display:flex;align-items:center;justify-content:center;background:#fafafa;border-top:1px solid #eee;}
129
  .foot a{font-size:0.85rem;font-weight:600;color:#4a6dd8;text-decoration:none;}
130
  .foot a:hover{text-decoration:underline;}
131
- /* ๋ฐ˜์‘ํ˜• ๋†’์ด */
132
  @media(min-width:1200px){.card{height:560px;}}
133
  @media(max-width:767px){
134
  .grid{grid-template-columns:1fr;}
@@ -136,9 +111,20 @@ def html(cards, pg, total):
136
  }
137
  </style>"""
138
 
139
- js = """
 
140
  <script>
141
- /* ํ—ˆ๊น…ํŽ˜์ด์Šค iframe ๋กœ๋”ฉ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ(์ƒ๋žต - ๊ธฐ์กด ์Šคํฌ๋ฆฝํŠธ ๊ทธ๋Œ€๋กœ) */
 
 
 
 
 
 
 
 
 
 
142
  </script>
143
  """
144
 
@@ -146,9 +132,9 @@ def html(cards, pg, total):
146
  for idx, url in enumerate(cards):
147
  iframe_url, extra_cls, alt_urls = process_url_for_iframe(url)
148
  frame_class = f"frame {extra_cls}".strip()
149
- iframe_id = f"iframe-{idx}-{hash(url) % 10000}"
150
  alt_attr = f'data-alternate-urls="{",".join(alt_urls)}"' if alt_urls else ""
151
- safe_url = to_hub_space_url(url) # ์ƒˆ ํƒญ ๋งํฌ์šฉ ๋ณ€ํ™˜ URL
152
  h += f"""
153
  <div class="card">
154
  <div class="{frame_class}">
@@ -157,9 +143,7 @@ def html(cards, pg, total):
157
  data-original-url="{url}" {alt_attr}></iframe>
158
  </div>
159
  <div class="foot">
160
- <a href="{safe_url}" target="_blank" rel="noopener noreferrer">
161
- โ†— Open in Full Screen (New Tab)
162
- </a>
163
  </div>
164
  </div>"""
165
  h += "</div></div>"
@@ -167,43 +151,44 @@ def html(cards, pg, total):
167
  return h
168
 
169
 
170
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 5. Gradio UI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
171
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 5. Gradio UI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
172
  def build():
173
  _init_best()
174
 
175
  header = """
176
  <style>
177
- .app-header{position:sticky;top:0;text-align:center;background:#fff;
178
- padding:16px 0 8px;border-bottom:1px solid #eee;z-index:1100;}
179
  .badge-row{display:inline-flex;gap:8px;margin:8px 0;}
180
  </style>
181
  <div class="app-header">
182
- <h1 style="margin:0;font-size:28px;">๐ŸŽฎ Vibe Game Gallery</h1>
183
- <h1 style="margin:0;font-size:11px;">Only high-quality games automatically generated with Vibe Game Craft are showcased in this gallery.
184
- Every game includes its full source code,</h1>
185
- <h1 style="margin:0;font-size:11px;"> and anyone can freely copy the index.html file from each URL and modify it as desired.
186
- All content is released under the Apache 2.0 license.</h1>
187
- <div class="badge-row">
188
- <a href="https://huggingface.co/spaces/openfree/Vibe-Game" target="_blank">
189
- <img src="https://img.shields.io/static/v1?label=huggingface&message=Vibe%20Game%20Craft&color=800080&labelColor=ffa500&logo=huggingface&logoColor=ffff00&style=for-the-badge">
190
- </a>
191
- <a href="https://huggingface.co/spaces/openfree/Game-Gallery" target="_blank">
192
- <img src="https://img.shields.io/static/v1?label=huggingface&message=Game%20Gallery&color=800080&labelColor=ffa500&logo=huggingface&logoColor=ffff00&style=for-the-badge">
193
- </a>
194
- <a href="https://discord.gg/openfreeai" target="_blank">
195
- <img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=0000ff&labelColor=800080&logo=discord&logoColor=white&style=for-the-badge">
196
- </a>
197
- </div>
 
198
  </div>"""
199
 
200
  global_css = """
201
  footer{display:none !important;}
202
- .button-row{position:fixed;bottom:0;left:0;right:0;height:60px;
203
- background:#f0f0f0;padding:10px;text-align:center;
204
- box-shadow:0 -2px 10px rgba(0,0,0,.05);z-index:1000;}
 
 
205
  .button-row button{margin:0 10px;padding:10px 20px;font-size:16px;font-weight:bold;border-radius:50px;}
206
- #content-area{overflow-y:auto;height:calc(100vh - 60px - 120px);}
207
  """
208
 
209
  with gr.Blocks(title="Vibe Game Gallery", css=global_css) as demo:
@@ -219,8 +204,8 @@ All content is released under the Apache 2.0 license.</h1>
219
  data, tot = page(_load_best(), p)
220
  return html(data, p, tot), p
221
 
222
- b_prev.click(lambda p: render(max(1, p - 1)), inputs=bp, outputs=[out, bp])
223
- b_next.click(lambda p: render(p + 1), inputs=bp, outputs=[out, bp])
224
 
225
  demo.load(render, outputs=[out, bp])
226
 
 
4
  import gradio as gr
5
 
6
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. ๊ธฐ๋ณธ ์„ค์ • โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
7
+ BEST_FILE, PER_PAGE = "best_games.json", 9 # โถ ํ•œ ํŽ˜์ด์ง€์— 9๊ฐœ์”ฉ
8
 
9
 
10
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 2. BEST ๋ฐ์ดํ„ฐ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
16
  def _load_best():
17
  try:
18
  raw = json.load(open(BEST_FILE))
19
+ return [u if isinstance(u, str) else u.get("url") for u in raw] if isinstance(raw, list) else []
 
 
 
20
  except Exception as e:
21
  print("BEST ๋กœ๋“œ ์˜ค๋ฅ˜:", e)
22
  return []
23
 
24
 
25
+ def _save_best(lst):
26
  try:
27
  json.dump(lst, open(BEST_FILE, "w"), ensure_ascii=False, indent=2)
28
  return True
 
31
  return False
32
 
33
 
34
+ # *.hf.space โ†’ Hub URL(์ƒˆ ํƒญ์šฉ) ๋ณ€ํ™˜
35
  def to_hub_space_url(url: str) -> str:
 
 
 
 
36
  m = re.match(r"https?://([^-]+)-([^.]+)\.hf\.space(/.*)?", url)
37
  if m:
38
  owner, space, _ = m.groups()
 
41
 
42
 
43
  def add_url_to_best(url: str):
 
44
  data = _load_best()
45
  if url in data:
46
  return False
 
56
 
57
 
58
  def process_url_for_iframe(url):
59
+ """iframe์šฉ ์ฃผ์†Œ ๋ณ€ํ™˜"""
 
 
 
 
60
  if "huggingface.co/spaces" in url:
61
  owner, name = url.rstrip("/").split("/spaces/")[1].split("/")[:2]
62
  return f"https://huggingface.co/spaces/{owner}/{name}/embed", "huggingface", []
63
 
 
64
  m = re.match(r"https?://([^/]+)\.hf\.space(/.*)?", url)
65
  if m:
66
  sub, rest = m.groups()
67
  static_url = f"https://{sub}.static.hf.space{rest or ''}"
 
68
  return static_url, "hfspace", [url]
69
 
70
  return url, "", []
71
 
72
 
73
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 4. HTML ๊ทธ๋ฆฌ๋“œ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
74
  def html(cards, pg, total):
75
  if not cards:
76
+ return "<div style='text-align:center;padding:70px;color:#555;'>ํ‘œ์‹œํ•  ๋ฐฐํฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.</div>"
 
 
 
77
 
78
  css = r"""
79
  <style>
80
+ /* ํŒŒ์Šคํ…” ๋ฐฐ๊ฒฝ */
81
  body{
82
  margin:0;padding:0;font-family:Poppins,sans-serif;
83
  background:linear-gradient(135deg,#fdf4ff 0%,#f6fbff 50%,#fffaf4 100%);
84
  background-attachment:fixed;
85
  overflow-x:hidden;overflow-y:auto;
86
  }
87
+ .container{width:100%;padding:10px 10px var(--bottom-gap,70px);box-sizing:border-box;}
88
  .grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:100%;}
89
  .card{
90
  background:#fff;border-radius:10px;overflow:hidden;box-shadow:0 4px 10px rgba(0,0,0,0.08);
91
  height:420px;display:flex;flex-direction:column;position:relative;
92
  }
 
93
  .frame{flex:1;position:relative;overflow:hidden;}
94
  .frame iframe{
95
  position:absolute;top:0;left:0;
96
  width:166.667%;height:166.667%;
97
  transform:scale(0.6);transform-origin:top left;border:0;
98
  }
99
+ .frame.huggingface iframe{width:100%!important;height:100%!important;transform:none!important;border:none!important;}
 
 
 
 
 
100
  .frame.hfspace iframe{
101
  width:200%;height:200%;
102
+ transform:scale(0.5);transform-origin:top left;border:0;
 
103
  }
 
104
  .foot{height:34px;display:flex;align-items:center;justify-content:center;background:#fafafa;border-top:1px solid #eee;}
105
  .foot a{font-size:0.85rem;font-weight:600;color:#4a6dd8;text-decoration:none;}
106
  .foot a:hover{text-decoration:underline;}
 
107
  @media(min-width:1200px){.card{height:560px;}}
108
  @media(max-width:767px){
109
  .grid{grid-template-columns:1fr;}
 
111
  }
112
  </style>"""
113
 
114
+ # ๋ฒ„ํŠผ๊ณผ ํ—ค๋” ๋†’์ด๋ฅผ ๊ณ ๋ คํ•ด ์Šคํฌ๋กค ์˜์—ญ ๋™์ ์œผ๋กœ ๊ณ„์‚ฐ
115
+ js = r"""
116
  <script>
117
+ function adjustGap(){
118
+ const header = document.querySelector('.app-header');
119
+ const buttons = document.querySelector('.button-row');
120
+ const gap = (buttons?.offsetHeight || 60) + 10; // 10px ์—ฌ์œ 
121
+ document.documentElement.style.setProperty('--bottom-gap', gap + 'px');
122
+ const content = document.getElementById('content-area');
123
+ const h = (header?.offsetHeight || 0) + (buttons?.offsetHeight || 60);
124
+ content.style.height = `calc(100vh - ${h}px)`;
125
+ }
126
+ window.addEventListener('load',adjustGap);
127
+ window.addEventListener('resize',adjustGap);
128
  </script>
129
  """
130
 
 
132
  for idx, url in enumerate(cards):
133
  iframe_url, extra_cls, alt_urls = process_url_for_iframe(url)
134
  frame_class = f"frame {extra_cls}".strip()
135
+ iframe_id = f"iframe-{idx}-{hash(url)%10000}"
136
  alt_attr = f'data-alternate-urls="{",".join(alt_urls)}"' if alt_urls else ""
137
+ safe_url = to_hub_space_url(url)
138
  h += f"""
139
  <div class="card">
140
  <div class="{frame_class}">
 
143
  data-original-url="{url}" {alt_attr}></iframe>
144
  </div>
145
  <div class="foot">
146
+ <a href="{safe_url}" target="_blank" rel="noopener noreferrer">โ†— Open in Full Screen (New Tab)</a>
 
 
147
  </div>
148
  </div>"""
149
  h += "</div></div>"
 
151
  return h
152
 
153
 
 
154
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 5. Gradio UI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
155
  def build():
156
  _init_best()
157
 
158
  header = """
159
  <style>
160
+ .app-header{position:sticky;top:0;text-align:center;background:#fff;padding:16px 0 8px;border-bottom:1px solid #eee;z-index:1100;}
 
161
  .badge-row{display:inline-flex;gap:8px;margin:8px 0;}
162
  </style>
163
  <div class="app-header">
164
+ <h1 style="margin:0;font-size:28px;">๐ŸŽฎ Vibe Game Gallery</h1>
165
+ <p style="margin:4px 0;font-size:11px;">
166
+ Only high-quality games automatically generated with <b>Vibe Game Craft</b> are showcased here.<br>
167
+ Every game includes its full source code, and anyone can freely copy the <code>index.html</code>
168
+ file from each URL and modify it as desired. All content is released under the <b>Apache&nbsp;2.0</b> license.
169
+ </p>
170
+ <div class="badge-row">
171
+ <a href="https://huggingface.co/spaces/openfree/Vibe-Game" target="_blank">
172
+ <img src="https://img.shields.io/static/v1?label=huggingface&message=Vibe%20Game%20Craft&color=800080&labelColor=ffa500&logo=huggingface&logoColor=ffff00&style=for-the-badge">
173
+ </a>
174
+ <a href="https://huggingface.co/spaces/openfree/Game-Gallery" target="_blank">
175
+ <img src="https://img.shields.io/static/v1?label=huggingface&message=Game%20Gallery&color=800080&labelColor=ffa500&logo=huggingface&logoColor=ffff00&style=for-the-badge">
176
+ </a>
177
+ <a href="https://discord.gg/openfreeai" target="_blank">
178
+ <img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=0000ff&labelColor=800080&logo=discord&logoColor=white&style=for-the-badge">
179
+ </a>
180
+ </div>
181
  </div>"""
182
 
183
  global_css = """
184
  footer{display:none !important;}
185
+ .button-row{
186
+ position:fixed!important;bottom:0!important;left:0!important;right:0!important;
187
+ height:60px;background:#f0f0f0;padding:10px;text-align:center;
188
+ box-shadow:0 -2px 10px rgba(0,0,0,.05);z-index:10000;
189
+ }
190
  .button-row button{margin:0 10px;padding:10px 20px;font-size:16px;font-weight:bold;border-radius:50px;}
191
+ #content-area{overflow-y:auto;}
192
  """
193
 
194
  with gr.Blocks(title="Vibe Game Gallery", css=global_css) as demo:
 
204
  data, tot = page(_load_best(), p)
205
  return html(data, p, tot), p
206
 
207
+ b_prev.click(lambda p: render(max(1, p-1)), inputs=bp, outputs=[out, bp])
208
+ b_next.click(lambda p: render(p+1), inputs=bp, outputs=[out, bp])
209
 
210
  demo.load(render, outputs=[out, bp])
211