import os, re, time, json, datetime, requests, gradio as gr # ───────────────────── 1. 기본 설정 ───────────────────── BEST_FILE, PER_PAGE = "best_games.json", 9 # ───────────────────── 2. BEST 데이터 ──────────────────── def _init_best(): if not os.path.exists(BEST_FILE): json.dump([], open(BEST_FILE, "w")) def _load_best(): try: data = json.load(open(BEST_FILE)) for it in data: if "ts" not in it: it["ts"] = int(it.get("timestamp", time.time())) return data except Exception as e: print(f"BEST 데이터 로드 오류: {e}") return [] def _save_best(data): try: json.dump(data, open(BEST_FILE, "w")) return True except Exception as e: print(f"BEST 데이터 저장 오류: {e}") return False # ───────────────────── 3. URL 추가 기능 ───────────────────── def add_url_to_best(title, url): """사용자가 제공한 URL을 BEST 목록에 추가합니다.""" try: # 현재 BEST 데이터 로드 data = _load_best() # URL이 이미 존재하는지 확인 for item in data: if item.get("url") == url: print(f"URL이 이미 존재합니다: {url}") return False # 새 항목 추가 new_item = { "title": title, "url": url, "ts": int(time.time()), "projectId": "", # 사용자가 직접 추가하므로 projectId 없음 "deploymentId": "" # 사용자가 직접 추가하므로 deploymentId 없음 } data.append(new_item) # 시간순으로 정렬 data = sorted(data, key=lambda x: x["ts"], reverse=True) # 저장 if _save_best(data): print(f"URL이 성공적으로 추가되었습니다: {url}") return True return False except Exception as e: print(f"URL 추가 오류: {str(e)}") return False # ───────────────────── 4. 페이지네이션 ─────────────────── def page(lst, pg): s = (pg-1) * PER_PAGE e = s + PER_PAGE total = (len(lst) + PER_PAGE - 1) // PER_PAGE return lst[s:e], total # ───────────────────── 5. HTML 그리드 ─────────────────── def html(cards, pg, total): if not cards: return "
표시할 배포가 없습니다.
" css = r""" """ h = css + """
""" for c in cards: date = datetime.datetime.fromtimestamp(int(c["ts"])).strftime("%Y-%m-%d") h += f"""

{c['title']}

{date}

""" h += """
""" # 페이지 정보 h += f'
Page {pg} / {total}
' return h # ───────────────────── 6. Gradio Blocks UI ─────────────────── def build(): _init_best() with gr.Blocks(title="Vibe Game Craft", css=""" /* Fix container sizing and prevent scrolling */ html, body, .gradio-container { height: 100vh !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; } footer {display: none !important;} .button-row { position: fixed !important; bottom: 0 !important; left: 0 !important; right: 0 !important; background: #f0f0f0 !important; padding: 10px !important; text-align: center !important; box-shadow: 0 -2px 10px rgba(0,0,0,0.05) !important; margin: 0 !important; z-index: 1000 !important; } .button-row button { margin: 0 10px !important; padding: 10px 20px !important; font-size: 16px !important; font-weight: bold !important; border-radius: 50px !important; } /* Fix component height */ #component-0 { height: calc(100vh - 60px) !important; overflow: hidden !important; } """) as demo: # 상태 및 출력 bp = gr.State(1) out = gr.HTML() # 페이지 네비게이션 (맨 아래 고정) with gr.Row(elem_classes="button-row"): b_prev = gr.Button("◀ 이전", size="lg") b_next = gr.Button("다음 ▶", size="lg") def show_best(p=1): d, t = page(_load_best(), p) return html(d, p, t), p def prev(b): b = max(1, b-1) h, _ = show_best(b) return h, b def nxt(b): maxp = (len(_load_best()) + PER_PAGE - 1) // PER_PAGE b = min(maxp, b+1) h, _ = show_best(b) return h, b # 이벤트 연결 b_prev.click(prev, inputs=[bp], outputs=[out, bp]) b_next.click(nxt, inputs=[bp], outputs=[out, bp]) # 초기 로드 demo.load(show_best, outputs=[out, bp]) return demo app = build() if __name__ == "__main__": app.launch()