openfree commited on
Commit
e33cada
·
verified ·
1 Parent(s): 388e87a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +526 -425
app.py CHANGED
@@ -8,7 +8,6 @@ import json
8
  import asyncio
9
  import requests
10
  import anthropic
11
- import openai
12
  import io
13
  import logging
14
 
@@ -21,174 +20,361 @@ import modelscope_studio.components.base as ms
21
  import modelscope_studio.components.legacy as legacy
22
  import modelscope_studio.components.antd as antd
23
 
24
- # === [1] 로거 설정 ===
25
  log_stream = io.StringIO()
26
  handler = logging.StreamHandler(log_stream)
27
  logger = logging.getLogger()
28
- logger.setLevel(logging.DEBUG) # 원하는 레벨로 설정
29
  logger.addHandler(handler)
30
 
31
  def get_logs():
32
- """StringIO 쌓인 로그를 문자열로 반환"""
33
  return log_stream.getvalue()
34
 
35
  import re
36
 
37
- deploying_flag = False # 전역 플래그
38
 
39
 
40
  def get_deployment_update(code_md: str):
 
41
  clean = remove_code_block(code_md)
42
  result = deploy_to_vercel(clean)
43
 
44
  m = re.search(r"https?://[\w\.-]+\.vercel\.app", result)
45
- if m: # ── 성공 ──
46
  url = m.group(0)
47
  md = (
48
- "✅ **배포 완료!**\n\n"
49
- f"➡️ [배포된 열기]({url})"
50
  )
51
- else: # ── 실패 ──
52
  md = (
53
- "❌ **배포 실패**\n\n"
54
  f"```\n{result}\n```"
55
  )
56
 
57
- # Markdown 컴포넌트이므로 gr.update 로 값/표시 전환
58
  return gr.update(value=md, visible=True)
59
 
60
 
61
  # ------------------------
62
- # 1) DEMO_LIST SystemPrompt
63
  # ------------------------
64
 
65
-
66
  DEMO_LIST = [
67
- {"description": "블록이 위에서 떨어지는 클래식 테트리스 게임을 개발해주세요. 화살표 키로 조작하며, 가로줄이 채워지면 해당 줄이 제거되고 점수가 올라가는 메커니즘이 필요합니다. 난이도는 시간이 지날수록 블록이 빨라지도록 구현하고, 게임오버 조건과 점수 표시 기능을 포함해주세요."},
68
- {"description": "두 명이 번갈아가며 플레이할 수 있는 체스 게임을 만들어주세요. 기본적인 체스 규칙(킹, 퀸, 룩, 비숍, 나이트, 폰의 이동 규칙)을 구현하고, 체크와 체크메이트 감지 기능이 필요합니다. 드래그 앤 드롭으로 말을 움직일 수 있게 하며, 이동 기록도 표시해주세요."},
69
- {"description": "짝을 맞추는 메모리 카드 게임을 개발해주세요. 카드를 뒤집으면 그림이 나타나고, 같은 그림의 카드 두 장을 찾으면 점수를 얻는 방식입니다. 카드 뒤집기 애니메이션과 함께 시도 횟수를 기록하는 점수 시스템, 그리고 쉬움/보통/어려움 난이도 선택 기능(카드 수 변경)도 구현해주세요."},
70
- {"description": "플레이어가 우주선을 조종하여 우주선을 파괴하는 슈팅 게임을 만들어주세요. 키보드 방향키로 움직이고 스페이스바로 발사하며, 다양한 적 웨이브가 공격해오는 구조입니다. 충돌 감지 시스템과 함께 파워업 아이템(방패, 다중 발사, 속도 증가 등)을 구현하고, 난이도가 점진적으로 증가하는 시스템을 추가해주세요."},
71
- {"description": "3x3 또는 4x4 크기의 슬라이드 퍼즐 게임을 만들어주세요. 숫자나 이미지 조각을 섞은 후, 칸을 이용해 조각들을 올바른 위치로 밀어 맞추는 게임입니다. 섞기 기능과 이동 횟수 카운터, 완성 시 축하 메시지를 표시하고, 난이도 설정(크기 변경)도 구현해주세요."},
72
- {"description": "고전적인 뱀 게임을 구현해주세요. 플레이어는 방향키로 뱀을 조종하여 필드에 랜덤하게 생성되는 먹이를 먹으며, 먹이를 먹을 때마다 뱀의 길이가 늘어납니다. 자신의 몸에 부딪히거나 벽에 부딪히면 게임이 종료되며, 점수는 먹은 먹이의 수에 비례합니다. 시간이 지날수록 뱀의 이동 속도가 빨라지는 난이도 조절 기능도 추가해주세요."},
73
- {"description": "화면 상단에 여러 줄의 벽돌이 배치된 브레이크아웃 게임을 만들어주세요. 플레이어는 화면 하단의 패들을 좌우로 움직여 공을 튕겨내어 벽돌을 깨야 합니다. 벽돌을 모두 깨면 스테이지 클리어, 공이 바닥에 떨어지면 생명이 감소합니다. 공의 속도는 시간이 지날수록 증가하며, 특수 벽돌(추가 생명, 패들 확장 등)도 구현해주세요."},
74
- {"description": "길을 따라 이동하는 적들을 방어하는 타워 디펜스 게임을 개발해주세요. 플레이어는 맵의 특정 위치에 다양한 타워(기본 공격, 범위 공격, 감속 효과 등)를 설치하여 적을 물리쳐야 합니다. 웨이브 시스템으로 난이도가 점진적으로 증가하며, 적을 처치하면 자원을 얻어 타워를 업그레이드하거나 새 타워를 건설할 수 있는 경제 시스템을 구현해주세요."},
75
- {"description": "캐릭터가 끝없이 달리며 장애물을 뛰어넘는 엔드리스 러너 게임을 만들어주세요. 스페이스바나 마우스 클릭으로 점프하여 다가오는 장애물(바위, 구덩이, 적 등)을 피해야 합니다. 거리에 따라 점수가 증가하며, 코인 등의 수집품을 모으는 요소와 파워업(일시적 무적, 자석 효과 등)도 추가해주세요. 시간이 지날수록 게임 속도가 빨라지는 난이도 시스템도 구현해주세요."},
76
- {"description": "2D 플랫포머 게임을 개발해주세요. 플레이어는 방향키로 캐릭터를 조종하여 발판 위를 이동하고, 스페이스바로 점프하며 코인이나 보석 같은 아이템을 수집합니다. 적 캐릭터(간단한 AI로 움직임)와 함정(가시, 떨어지는 발판 등)을 피해 목표 지점까지 도달하는 레벨 기반 구조로 만들어주세요. 체력 시스템과 체크포인트 기능도 구현해주세요."},
77
- {"description": "매번 새로운 미로를 자동 생성하는 미로 게임을 만들어주세요. 플레이어는 시작점에서 출발하여 방향키로 캐릭터를 조종해 출구를 찾아야 합니다. 미로 생성 알고리즘(예: 깊이 우선 탐색, 프림 알고리즘 ) 활용하여 다양한 크기와 복잡도의 미로를 만들고, 타이머로 시간을 측정하며, 선택적으로 최단 경로를 보여주는 힌트 기능도 구현해주세요."},
78
- {"description": "간단한 턴제 RPG 게임을 개발해주세요. 플레이어는 탑다운 뷰에서 타일 기반으로 이동하며, 몬스터와 마주치면 턴제 전투가 시작됩니다. 기본 공격, 특수 스킬, 아이템 사용 등의 전투 옵션과 함께 레벨업 시스템(경험치, 능력치 상승)을 구현해주세요. 또한 전투에서 승리하면 골드와 아이템을 획득할 수 있으며, 상점에서 장비를 구매하는 기능도 추가해주세요."},
79
- {"description": "같은 색상/모양의 아이템 3개 이상을 일렬로 맞추는 매치-3 퍼즐 게임을 만들어주세요. 아이템을 스와이프하여 위치를 바꾸고, 매치되면 아이템이 사라지며 점수를 얻는 방식입니다. 특수 매치(4개 이상, T자 모양 등)는 특수 아이템을 생성하며, 연속 매치(콤보)는 추가 점수나 보너스 효과를 제공합니다. 목표 점수 또는 제한 시간/이동 횟수 모드를 구현해주세요."},
80
- {"description": "플래피 버드 스타일의 게임을 개발해주세요. 플레이어는 스페이스바나 마우스 클릭으로 새를 점프시켜 위아래로 움직이는 파이프 사이를 통과해야 합니다. 파이프에 부딪히거나 화면 상단/하단에 닿으면 게임 오버이며, 통과한 파이프 쌍마다 점수가 1점씩 증가합니다. 파이프 간격은 랜덤하게 생성되며, 최고 점수를 로컬 스토리지에 저장하는 기능도 구현해주세요."},
81
- {"description": "두 개의 유사한 이미지에서 차이점을 찾는 게임을 만들어주세요. 5-10개의 차이점이 있는 이미지 쌍을 준비하고, 플레이어가 차이점을 클릭하면 표시되도록 합니다. 제한 시간 내에 모든 차이점을 찾아야 하며, 오답 클릭 시 시간 패널티가 부과됩니다. 힌트 시스템(차이점 하나를 자동으로 표시)과 난이도 선택(쉬움: 차이점이 명확, 어려움: 미묘한 차이)도 구현해주세요."},
82
- {"description": "화면 상단에서 단어가 떨어지는 타이핑 게임을 개발해주세요. 플레이어는 키보드로 해당 단어를 정확히 입력하여 단어가 바닥에 닿기 전에 제거해야 합니다. 정확히 입력한 단어는 사라지고 점수를 얻으며, 난이도에 따라 단어의 길이와 떨어지는 속도가 조절됩니다. 특수 단어(빨간색 등)는 보너스 점수나 시간 추가 등의 효과를 제공하며, 일정 시간/점수마다 난이도가 상승하는 시스템도 구현해주세요."},
83
- {"description": "물리 엔진 기반의 미니 골프 게임을 만들어주세요. 플레이어는 마우스 드래그로 공을 치는 방향과 세기를 조절하여 홀에 공을 넣어야 합니다. 다양한 장애물(모래 함정, 물웅덩이, 경사로 등)이 있는 여러 개의 코스를 구현하고, 각 홀마다 타수를 기록하여 최종 점수를 계산합니다. 바람 방향/세기 같은 환경 요소와 함께 궤적 미리보기 기능도 추가해주세요."},
84
- {"description": "플레이어가 낚시를 즐기는 시뮬레이션 게임을 개발해주세요. 마우스 클릭으로 낚싯줄을 던지고, 물고기가 물면 타이밍 맞추기 미니게임으로 물고기를 낚아야 합니다. 다양한 종류의 물고기(희귀도별 점수 차등)를 구현하고, 낚은 물고기에 따라 골드를 획득하여 더 좋은 낚싯대, 미끼 등을 구매할 수 있는 업그레이드 시스템을 추가해주세요. 시간대나 날씨에 따라 출현하는 물고기가 달라지는 기능도 구현해주세요."},
85
- {"description": "1인용 또는 AI 대전 빙고 게임을 만들어주세요. 5x5 그리드에 1-25 숫자를 무작위로 배치하고, 번갈아가며 숫자를 선택하여 해당 칸을 마킹합니다. 가로, 세로, 대각선으로 5개의 연속된 마킹이 완성되면 빙고가 되며, 먼저 3빙고를 달성하는 쪽이 승리합니다. 컴퓨터 AI는 랜덤하게 또는 전략적으로(빙고에 가까운 라인 우선) 숫자를 선택하도록 구현하고, 타이머와 승/패 기록 시스템도 추가해주세요."},
86
- {"description": "화면 하단에서 상단으로 노트가 올라오면 정확한 타이밍에 키를 눌러 점수를 얻는 리듬 게임을 개발해주세요. 4개의 레인(D, F, J, K 키)에 노트가 등장하며, 타이밍 정확도에 따라 Perfect, Good, Miss 등급이 표시됩니다. 배경 음악에 맞춰 노트가 생성되며, 연속 성공 시 콤보 시스템으로 추가 점수를 제공합니다. 난이도 선택(노트 속도와 밀도 조절)과 함께 최종 결과 화면(정확도, 콤보, 등급)도 구현해주세요."},
87
- {"description": "탑다운 뷰의 2D 레이싱 게임을 만들어주세요. 플레이어는 방향키로 자동차를 조종하여 트랙을 따라 주행하며, 트랙 이탈 시 감속되는 메커니즘을 구현합니다. 여러 AI 경쟁자들과 경쟁하며 3바퀴를 가장 빨리 완주하는 게임 모드와 함께, 시간 제한 내에 체크포인트를 통과하는 타임 어택 모드도 구현해주세요. 다양한 차량 선택지(속도와 핸들링 특성 차등)와 부스트 아이템, 장애물 등도 추가해주세요."},
88
- {"description": "다양한 카테고리의 퀴즈를 풀어나가는 게임을 개발해주세요. 주어진 질문에 4개의 보기 중 정답을 선택하는 방식으로, 정답 시 점수를 획득하고 오답 시 생명이 감소합니다. 30초 제한 시간 내에 답을 선택해야 하며, 난이도에 따라 질문의 복잡도와 제한 시간이 조절됩니다. 50:50 힌트(오답 2개 제거), 시간 추가 등의 도움 아이템과 함께 최종 결과 요약(정답률, 카테고리별 성적)도 구현해주세요."},
89
- {"description": "움직이는 표적을 맞추는 사격 갤러리 게임을 만들어주세요. 마우스 클릭으로 발사하며, 다양한 속도와 패턴으로 움직이는 표적(오리, 병, 풍선 등)을 맞추면 점수를 획득합니다. 제한된 시간과 총알 수 안에 최대한 많은 점수를 얻는 것이 목표이며, 특수 표적(황금 표적 등)은 보너스 점수나 추가 시간/총알을 제공합니다. 연속 명중 시 점수 배율이 증가하는 콤보 시스템과 함께 다양한 난이도 레벨(표적 속도/수 증가)도 구현해주세요."},
90
- {"description": "가상 주사위를 굴려 보드판을 돌아다니는 보드 게임을 개발해주세요. 플레이어는 차례대로 1-6 주사위를 굴려 말을 이동시키며, 도착한 칸에 따라 다양한 이벤트(앞으로/뒤로 이동, 턴 쉬기, 미니게임 등) 발생합니다. 특수 아이템(추가 주사위, 이벤트 회피 등)을 수집하고 사용할 수 있으며, 먼저 결승점에 도달하거나 가장 많은 포인트를 모은 플레이어가 승리합니다. 1-4명의 로컬 멀티플레이어를 지원하며, AI 플레이어도 구현해주세요."},
91
- {"description": "탑다운 뷰의 좀비 서바이벌 게임을 만들어주세요. WASD로 이동하고 마우스로 조준/발사하며, 끊임없이 몰려오는 좀비 웨이브를 최대한 오래 생존하는 것이 목표입니다. 다양한 무기(권총, 샷건, 기관총 등)와 제한된 탄약, 그리고 체력 회복 아이템과 폭탄 같은 특수 아이템을 맵에서 획득할 수 있습니다. 시간이 지날수록 좀비의 수와 속도가 증가하며, 특수 좀비(탱커, 러너 등)도 등장하는 난이도 시스템을 구현해주세요."},
92
- {"description": "축구 페널티킥 게임을 개발해주세요. 공격 시에는 방향과 파워를 조절하여 슛을 날리고, 수비 시에는 골키퍼를 좌/중앙/우 중 한 방향으로 다이빙시켜 공을 막아야 합니다. 5번의 키커-골키퍼 대결 후 더 많은 골을 넣은 쪽이 승리하며, 동점일 경우 서든데스로 승부를 가립니다. 슛의 정확도와 파워에 따라 결과가 달라지며, 골키퍼 AI는 패턴 학습을 통해 플레이어의 경향성을 파악하도록 구현해주세요. 1인 플레이와 2인 로컬 대전 모드를 모두 지원해주세요."},
93
- {"description": "클래식한 지뢰찾기 게임을 구현해주세요. NxN 크기의 그리드에 M개의 지뢰가 무작위로 배치되며, 플레이어는 좌클릭으로 칸을 열고 우클릭으로 지뢰 위치에 깃발을 표시합니다. 열린 칸에는 주변 8칸의 지뢰 수가 표시되며, 주변에 지뢰가 없는 칸을 열면 연쇄적으로 주변 칸들이 열립니다. 지뢰가 있는 칸을 열면 게임 오버, 지뢰가 아닌 모든 칸을 열면 승리입니다. 난이도 설정(쉬움: 9x9/10개, 중간: 16x16/40개, 어려움: 30x16/99개)과 함께 첫 클릭은 항상 안전하도록 구현해주세요."},
94
- {"description": "두 플레이어가 번갈아가며 7x6 그리드에 색깔 디스크를 떨어뜨려 가로, 세로, 대각선으로 4개의 연속된 디스크를 만드는 Connect Four 게임을 개발해주세요. 플레이어는 열을 클릭하여 디스크를 해당 열의 가장 아래 빈 칸에 배치합니다. 4개의 연속된 디스크를 먼저 만드는 플레이어가 승리하며, 모든 칸이 차면 무승부입니다. 1인 플레이(AI 대전)과 2인 로컬 대전 모드를 구현하고, AI는 최소한 1단계 앞을 내다보는 논리로 작동하도록 해주세요."},
95
- {"description": "글자 타일을 배치하여 단어를 만드는 스크래블 스타일의 단어 게임을 만들어주세요. 각 플레이어는 7개의 글자 타일을 받고, 이를 보드에 배치하여 가로나 세로로 단어를 형성합니다. 새 단어는 기존 단어와 반드시 연결되어야 하며, 각 타일에는 점수가 있어 단어의 총점이 계산됩니다. 특수 칸(2배 글자 점수, 3배 단어 점수 등) 활용한 전략적 배치가 가능하며, 사전 검증 기능으로 유효한 단어만 허용합니다. 1-4인 로컬 멀티플레이어와 AI 대전을 지원해주세요."},
96
- {"description": "2D 환경에서 진행되는 탱크 전투 게임을 개발해주세요. 플레이어는 WASD로 탱크를 조종하고, 마우스로 포탑을 조준하여 클릭으로 발사합니다. 파괴 가능한 지형(벽돌, 나무 등)과 파괴 불가능한 장애물(강철, 물 등)이 있는 맵에서 적 탱크들과 전투를 벌입니다. 다양한 무기(기본 포탄, 확산탄, 레이저 등)와 아이템(속도 증가, 방어력 강화, 추가 생명 등)을 구현하고, 스테이지별로 증가하는 적 AI 난이도와 보스 전투도 추가해주세요."},
97
- {"description": "3개 이상의 같은 보석을 맞추어 제거하는 퍼즐 게임을 만들어주세요. 인접한 두 보석을 스왑하여 매치를 만들며, 매치된 보석이 사라지면 위의 보석들이 떨어지고 새 보석이 채워집니다. 4개 이상 매치 시 특수 보석(가로/세로 폭발, 주변 9칸 폭발 등)이 생성되며, 연쇄 매치가 발생하면 콤보 점수가 추가됩니다. 제한 시간 또는 제한 이동 횟수 내에 목표 점수를 달성하는 레벨 기반 진행 구조와 함께, 특수 미션(특정 색상 N개 제거, 장애물 파괴 등)도 구현해주세요."},
98
- {"description": "단일 타워가 끊임없이 몰려오는 적들을 격퇴하는 타워 디펜스 게임을 개발해주세요. 화면 중앙의 타워는 자동으로 가장 가까운 적을 향해 발사하며, 플레이어는 웨이브 사이에 획득한 자원으로 타워를 업그레이드(공격력, 공격 속도, 범위 등)할 수 있습니다. 시간이 지날수록 더 강력하고 다양한 적(빠른 적, 방어력 높은 적, 분열하는 적 등)이 등장하며, 타워의 체력이 0이 되면 게임 오버입니다. 특수 능력(범위 공격, 일시 정지, 즉시 회복 등)과 함께 생존한 웨이브 수에 따른 랭킹 시스템도 구현해주세요."},
99
- {"description": "캐릭터가 끝없이 달리며 좀비와 장애물을 피하는 사이드 스크롤링 러너 게임을 만들어주세요. 스페이스바로 점프, S키로 슬라이딩하여 다양한 장애물(웅덩이, 장벽, 좀비 무리 등)을 피해야 합니다. 코인과 파워업(일시적 무적, 자석 효과, 속도 감소 등)을 수집하며, 특정 구간마다 미니 보스 좀비와의 간단한 전투도 포함됩니다. 거리에 따라 점수가 증가하고, 코인으로 캐릭터 업그레이드(더블 점프, 체력 증가 등)를 구매할 수 있는 시스템도 구현해주세요."},
100
- {"description": "탑다운 뷰의 간단한 액션 RPG 게임을 개발해주세요. WASD로 이동하고, 마우스 클릭으로 기본 공격, 1-4 키로 특수 스킬을 사용합니다. 플레이어는 몬스터를 처치하며 경험치와 아이템을 획득하고, 레벨업 시 능력치(공격력, 체력, 속도 등)를 향상시킵니다. 다양한 무기와 방어구를 착용할 수 있으며, 스킬 트리 시스템으로 캐릭터를 특화시킬 수 있습니다. 여러 지역과 보스 몬스터, 간단한 퀘스트 시스템도 구현해주세요."},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  ]
102
 
103
-
104
  SystemPrompt = """
105
- # GameCraft 시스템 프롬프트
106
-
107
- ## 1. 기본 정보 역할
108
- 당신의 이름은 'GameCraft'입니다. 당신은 게임플레이 메커니즘, 인터랙티브 디자인, 성능 최적화에 뛰어난 게임 개발 전문가입니다. HTML, JavaScript, CSS를 활용하여 간결하고 효율적인 웹 기반 게임을 제작하는 것이 당신의 임무입니다.
109
-
110
- ## 2. 핵심 기술 스택
111
- - **프론트엔드**: HTML5, CSS3, JavaScript(ES6+)
112
- - **렌더링 방식**: 브라우저에서 직접 렌더링 가능한 코드 생성
113
- - **코드 스타일**: 바닐라 JavaScript 우선, 외부 라이브러리 최소화
114
-
115
- ## 3. 게임 유형별 특화 지침
116
- ### 3.1 아케이드/액션 게임
117
- - 간결한 충돌 감지 시스템 구현
118
- - 키보드/터치 입력 최적화
119
- - 기본적인 점수 시스템
120
-
121
- ### 3.2 퍼즐 게임
122
- - 명확한 게임 규칙 및 승리 조건
123
- - 기본 난이도 구현
124
- - 핵심 게임 메커니즘에 집중
125
-
126
- ### 3.3 카드/보드 게임
127
- - 간소화된 턴 기반 시스템
128
- - 기본 게임 규칙 자동화
129
- - 핵심 게임 로직 중심
130
-
131
- ### 3.4 시뮬레이션 게임
132
- - 효율적인 상태 관리
133
- - 가장 중요한 상호작용 구현
134
- - 핵심 요소만 포함
135
-
136
- ## 4. 이모지 활용 지침 🎮
137
- - 게임 UI 요소에 이모지 활용 (예: 생명력 ❤️, 코인 💰, 시간 ⏱️)
138
- - 이모지 사용은 핵심 요소에만 집중
139
-
140
- ## 5. 기술적 구현 가이드라인
141
- ### 5.1 코드 구조
142
- - **간결성 중시**: 코드는 최대한 간결하게 작성하고, 주석은 최소화
143
- - **모듈화**: 코드 기능별로 분리하되 불필요한 추상화 지양
144
- - **최적화**: 게임 루프와 렌더링 최적화에 집중
145
- - **코드 크기 제한**: 전체 코드는 200줄을 넘지 않도록 함
146
-
147
- ### 5.2 성능 최적화
148
- - DOM 조작 최소화
149
- - 불필요한 변수와 함수 제거
150
- - 메모리 관리에 주의
151
-
152
- ### 5.3 반응형 디자인
153
- - 기본적인 반응형 지원
154
- - 핵심 기능에 집중한 심플한 UI
155
-
156
- ## 6. 외부 라이브러리
157
- - 라이브러리 사용은 최소화하고, 필요한 경우에만 사용
158
- - 라이브러리 사용 CDN으로 가져올
159
-
160
- ## 7. 접근성 및 포용성
161
- - 핵심 접근성 기능에만 집중
162
-
163
- ## 8. 제약사항 및 유의사항
164
- - 외부 API 호출 금지
165
- - 코드 크기 최소화에 우선순위 (200줄 이내)
166
- - 주석 최소화 - 필수적인 설명만 포함
167
- - 불필요한 기능 구현 지양
168
-
169
- ## 9. 출력 형식
170
- - HTML 코드 블록으로만 코드 반환
171
- - 추가 설명 없이 즉시 실행 가능한 코드만 제공
172
- - 모든 코드는 단일 HTML 파일에 인라인으로 포함
173
-
174
- ## 10. 코드 품질 기준
175
- - 효율성과 간결함이 최우선
176
- - 핵심 게임플레이 메커니즘에만 집중
177
- - 복잡한 기능보다 작동하는 기본 기능 우선
178
- - 불필요한 주석이나 장황한 코드 지양
179
- - 단일 파일에 모든 코드 포함
180
- - 코드 길이 제한: 완성된 게임 코드는 200 이내로 작성
181
-
182
- ## 11. 중요: 코드 생성 제한
183
- - 게임 코드는 반드시 200 이내로 제한
184
- - 불필요한 설명이나 주석 제외
185
- - 핵심 기능만 구현하고 부가 기능은 생략
186
- - 코드 크기가 커질 경우 기능을 간소화하거나 생략할 것
187
  """
188
 
189
-
190
  # ------------------------
191
- # 2) 공통 상수, 함수, 클래스
192
  # ------------------------
193
 
194
  class Role:
@@ -202,9 +388,7 @@ Messages = List[Dict[str, str]]
202
  IMAGE_CACHE = {}
203
 
204
  def get_image_base64(image_path):
205
- """
206
- 이미지 파일을 base64로 읽어서 캐싱
207
- """
208
  if image_path in IMAGE_CACHE:
209
  return IMAGE_CACHE[image_path]
210
  try:
@@ -231,29 +415,29 @@ def messages_to_history(messages: Messages) -> History:
231
 
232
 
233
  # ------------------------
234
- # 3) API 연동 설정
235
  # ------------------------
236
 
237
  YOUR_ANTHROPIC_TOKEN = os.getenv('ANTHROPIC_API_KEY', '').strip()
238
- YOUR_OPENAI_TOKEN = os.getenv('OPENAI_API_KEY', '').strip()
239
-
240
  claude_client = anthropic.Anthropic(api_key=YOUR_ANTHROPIC_TOKEN)
241
- openai_client = openai.OpenAI(api_key=YOUR_OPENAI_TOKEN)
242
 
243
  async def try_claude_api(system_message, claude_messages, timeout=15):
244
  """
245
- Claude API 호출 (스트리밍)
246
  """
247
  try:
248
- system_message_with_limit = system_message + "\n\n추가 중요 지침: 생성하는 코드는 절대로 200줄을 넘지 마세요. 코드 간결성이 최우선입니다. 주석을 최소화하고, 핵심 기능만 구현하세요."
249
-
 
 
250
  start_time = time.time()
 
251
  with claude_client.messages.stream(
252
  model="claude-3-7-sonnet-20250219",
253
  max_tokens=19800,
254
  system=system_message_with_limit,
255
  messages=claude_messages,
256
- temperature=0.3,
257
  ) as stream:
258
  collected_content = ""
259
  for chunk in stream:
@@ -268,46 +452,23 @@ async def try_claude_api(system_message, claude_messages, timeout=15):
268
  except Exception as e:
269
  raise e
270
 
271
- async def try_openai_api(openai_messages):
272
- """
273
- OpenAI API 호출 (스트리밍) - 코드 길이 제한 강화
274
- """
275
- try:
276
- if openai_messages and openai_messages[0]["role"] == "system":
277
- openai_messages[0]["content"] += "\n\n추가 중요 지침: 생성하는 코드는 절대로 200줄을 넘지 마세요. 코드 간결성이 최우선입니다. 주석은 최소화하고, 핵심 기능만 구현하세요."
278
-
279
- stream = openai_client.chat.completions.create(
280
- model="o3",
281
- messages=openai_messages,
282
- stream=True,
283
- max_tokens=19800,
284
- temperature=0.2
285
- )
286
- collected_content = ""
287
- for chunk in stream:
288
- if chunk.choices[0].delta.content is not None:
289
- collected_content += chunk.choices[0].delta.content
290
- yield collected_content
291
- except Exception as e:
292
- raise e
293
-
294
 
295
  # ------------------------
296
- # 4) 템플릿(하나로 통합)
297
  # ------------------------
298
 
299
  def load_json_data():
300
  data_list = []
301
  for item in DEMO_LIST:
302
  data_list.append({
303
- "name": f"[게임] {item['description'][:20]}...",
304
  "prompt": item['description']
305
  })
306
  return data_list
307
 
308
  def create_template_html(title, items):
309
  """
310
- 이미지 없이 템플릿 HTML 생성
311
  """
312
  html_content = r"""
313
  <style>
@@ -362,6 +523,7 @@ def create_template_html(title, items):
362
  """
363
  html_content += card_html
364
  html_content += r"""
 
365
  <script>
366
  function copyToInput(card) {
367
  const prompt = card.dataset.prompt;
@@ -369,21 +531,19 @@ function copyToInput(card) {
369
  if (textarea) {
370
  textarea.value = prompt;
371
  textarea.dispatchEvent(new Event('input', { bubbles: true }));
372
- // 템플릿 Drawer 닫기
373
  document.querySelector('.session-drawer .close-btn').click();
374
  }
375
  }
376
  </script>
377
- </div>
378
  """
379
  return gr.HTML(value=html_content)
380
 
381
  def load_all_templates():
382
- return create_template_html("🎮 모든 게임 템플릿", load_json_data())
383
 
384
 
385
  # ------------------------
386
- # 5) 배포/부스트/기타 유틸
387
  # ------------------------
388
 
389
  def remove_code_block(text):
@@ -402,6 +562,7 @@ def remove_code_block(text):
402
  return text.strip()
403
 
404
  def optimize_code(code: str) -> str:
 
405
  if not code or len(code.strip()) == 0:
406
  return code
407
 
@@ -409,6 +570,7 @@ def optimize_code(code: str) -> str:
409
  if len(lines) <= 200:
410
  return code
411
 
 
412
  comment_patterns = [
413
  r'/\*[\s\S]*?\*/',
414
  r'//.*?$',
@@ -418,6 +580,7 @@ def optimize_code(code: str) -> str:
418
  for pattern in comment_patterns:
419
  cleaned_code = re.sub(pattern, '', cleaned_code, flags=re.MULTILINE)
420
 
 
421
  cleaned_lines = []
422
  empty_line_count = 0
423
  for line in cleaned_code.split('\n'):
@@ -430,7 +593,9 @@ def optimize_code(code: str) -> str:
430
  cleaned_lines.append(line)
431
 
432
  cleaned_code = '\n'.join(cleaned_lines)
 
433
  cleaned_code = re.sub(r'console\.log\(.*?\);', '', cleaned_code, flags=re.MULTILINE)
 
434
  cleaned_code = re.sub(r' {2,}', ' ', cleaned_code)
435
  return cleaned_code
436
 
@@ -460,52 +625,37 @@ def send_to_sandbox(code):
460
  return f'<iframe src="{data_uri}" width="100%" height="920px" style="border:none;"></iframe>'
461
 
462
  def boost_prompt(prompt: str) -> str:
 
 
 
 
463
  if not prompt:
464
  return ""
465
- boost_system_prompt = """당신은 웹 게임 개발 프롬프트 전문가입니다.
466
- 주어진 프롬프트를 분석하여 명확하고 간결한 요구사항으로 변환하되,
467
- 원래 의도와 목적은 그대로 유지하면서 다음 관점들을 고려하여 증강하십시오:
468
-
469
- 1. 게임플레이 핵심 메커니즘 명확히 정의
470
- 2. 필수적인 상호작용 요소만 포함
471
- 3. 핵심 UI 요소 간략히 기술
472
- 4. 코드 간결성 유지를 위한 우선순위 설정
473
- 5. 기본적인 게임 규칙과 승리/패배 조건 명시
474
-
475
- 다음 중요 지침을 반드시 준수하세요:
476
- - 불필요한 세부 사항이나 부가 기능은 제외
477
- - 생성될 코드가 600줄을 넘지 않도록 기능을 제한
478
- - 명확하고 간결한 언어로 요구사항 작성
479
- - 최소한의 필수 게임 요소만 포함
480
- """
481
  try:
482
- try:
483
- response = claude_client.messages.create(
484
- model="claude-3-7-sonnet-20250219",
485
- max_tokens=10000,
486
- temperature=0.3,
487
- messages=[{
488
- "role": "user",
489
- "content": f"다음 게임 프롬프트를 분석하고 증강하되, 간결함을 유지하세요: {prompt}"
490
- }],
491
- system=boost_system_prompt
492
- )
493
- if hasattr(response, 'content') and len(response.content) > 0:
494
- return response.content[0].text
495
- raise Exception("Claude API 응답 형식 오류")
496
- except Exception:
497
- completion = openai_client.chat.completions.create(
498
- model="gpt-4",
499
- messages=[
500
- {"role": "system", "content": boost_system_prompt},
501
- {"role": "user", "content": f"다음 게임 프롬프트를 분석하고 증강하되, 간결함을 유지하세요: {prompt}"}
502
- ],
503
- max_tokens=10000,
504
- temperature=0.3
505
- )
506
- if completion.choices and len(completion.choices) > 0:
507
- return completion.choices[0].message.content
508
- raise Exception("OpenAI API 응답 형식 오류")
509
  except Exception:
510
  return prompt
511
 
@@ -520,6 +670,7 @@ def history_render(history: History):
520
  return gr.update(open=True), history
521
 
522
  def execute_code(query: str):
 
523
  if not query or query.strip() == '':
524
  return None, gr.update(active_key="empty")
525
  try:
@@ -528,19 +679,18 @@ def execute_code(query: str):
528
  clean_code = clean_code[7:].strip()
529
  if clean_code.endswith('```'):
530
  clean_code = clean_code[:-3].strip()
531
- if not clean_code.strip().startswith('<!DOCTYPE') and not clean_code.strip().startswith('<html'):
 
532
  if not ('<body' in clean_code and '</body>' in clean_code):
533
- clean_code = f"""<!DOCTYPE html>
534
- <html>
535
- <head>
536
- <meta charset="UTF-8">
537
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
538
- <title>Game Preview</title>
539
- </head>
540
- <body>
541
- {clean_code}
542
- </body>
543
- </html>"""
544
  return send_to_sandbox(clean_code), gr.update(active_key="render")
545
  except Exception as e:
546
  print(f"Execute code error: {str(e)}")
@@ -548,87 +698,94 @@ def execute_code(query: str):
548
 
549
 
550
  # ------------------------
551
- # 6) 데모 클래스
552
  # ------------------------
553
- # ── deploy 버튼용 함수 새로 작성 ─────────────────────────────
554
  def deploy_and_show(code_md: str):
 
 
 
555
  global deploying_flag
556
 
557
- # 중복 클릭 차단 ---------------------------------
558
  if deploying_flag:
559
- return (gr.update(value="⏳ 이미 배포 중입니다...", visible=True),
560
- None,
561
- gr.update(active_key="empty"))
 
 
562
  deploying_flag = True
563
 
564
- # ② 코드 정리 & 배포 --------------------------------
565
  clean = remove_code_block(code_md)
566
  result = deploy_to_vercel(clean)
567
 
568
- # 결과 해석 ---------------------------------------
569
-
570
- url_match = re.search(r"https?://[\w\.-]+\.vercel\.app", result)
571
- if url_match:
572
- url = url_match.group(0)
573
-
574
- md_out = f"✅ **배포 완료!**\n\n➡️ [열기]({url})"
575
- iframe = (f"<iframe src='{url}' width='100%' height='920px' "
576
- "style='border:none;'></iframe>")
577
-
578
  deploying_flag = False
579
- return (gr.update(value=md_out, visible=True),
580
- iframe,
581
- gr.update(active_key="render"))
 
 
582
 
583
- # 실패 처리 ---------------------------------------
584
- md_err = f"❌ **배포 실패**\n\n```\n{result}\n```"
585
  deploying_flag = False
586
- return (gr.update(value=md_err, visible=True),
587
- None,
588
- gr.update(active_key="empty"))
589
-
590
-
591
 
592
  class Demo:
593
  def __init__(self):
594
  pass
595
-
596
- async def generation_code(self, query: Optional[str], _setting: Dict[str, str], _history: Optional[History]):
 
 
 
 
 
 
 
 
597
  if not query or query.strip() == '':
598
  query = random.choice(DEMO_LIST)['description']
599
 
600
  if _history is None:
601
  _history = []
602
 
603
- query = f"""
604
- 다음 게임을 제작해주세요.
605
- 중요 요구사항:
606
- 1. 코드는 가능한 간결하게 작성할
607
- 2. 불필요한 주석이나 설명은 제외할 것
608
- 3. 코드는 600줄을 넘지 않을
609
- 4. 모든 코드는 하나의 HTML 파일에 통합할 것
610
- 5. 핵심 기능만 구현하고 부가 기능은 생략할 것
611
- 게임 요청: {query}
612
- """
613
 
614
  messages = history_to_messages(_history, _setting['system'])
615
  system_message = messages[0]['content']
616
-
 
617
  claude_messages = [
618
- {"role": msg["role"] if msg["role"] != "system" else "user", "content": msg["content"]}
619
- for msg in messages[1:] + [{'role': Role.USER, 'content': query}]
 
 
 
620
  if msg["content"].strip() != ''
621
  ]
622
-
623
- openai_messages = [{"role": "system", "content": system_message}]
624
- for msg in messages[1:]:
625
- openai_messages.append({
626
- "role": msg["role"],
627
- "content": msg["content"]
628
- })
629
- openai_messages.append({"role": "user", "content": query})
630
-
631
  try:
 
632
  yield [
633
  "Generating code...",
634
  _history,
@@ -637,58 +794,48 @@ class Demo:
637
  gr.update(open=True)
638
  ]
639
  await asyncio.sleep(0)
 
640
  collected_content = None
641
- try:
642
- async for content in try_claude_api(system_message, claude_messages):
643
- yield [
644
- content,
645
- _history,
646
- None,
647
- gr.update(active_key="loading"),
648
- gr.update(open=True)
649
- ]
650
- await asyncio.sleep(0)
651
- collected_content = content
652
- except Exception:
653
- async for content in try_openai_api(openai_messages):
654
- yield [
655
- content,
656
- _history,
657
- None,
658
- gr.update(active_key="loading"),
659
- gr.update(open=True)
660
- ]
661
- await asyncio.sleep(0)
662
- collected_content = content
663
 
664
  if collected_content:
665
  clean_code = remove_code_block(collected_content)
666
  code_lines = clean_code.count('\n') + 1
667
  if code_lines > 700:
668
- warning_msg = f"""
669
- ⚠️ **경고: 생성된 코드가 너무 깁니다 ({code_lines})**
670
- 이로 인해 실행 오류가 발생할 있습니다. 다음과 같이 시도해 보세요:
671
- 1. 간단한 게임을 요청하세요
672
- 2. 특정 기능만 명시하여 요청하세요 (예: "간단한 Snake 게임, 점수 시스템 없이")
673
- 3. "코드" 버튼을 사용하여 직접 실행해 보세요
674
- ```html
675
- {clean_code[:2000]}
676
- ... (코드가 너무 깁니다) ... """
677
- collected_content = warning_msg
678
  yield [
679
- collected_content,
680
  _history,
681
  None,
682
  gr.update(active_key="empty"),
683
  gr.update(open=True)
684
  ]
685
  else:
686
- _history = messages_to_history([
687
- {'role': Role.SYSTEM, 'content': system_message}
688
- ] + claude_messages + [{
689
- 'role': Role.ASSISTANT,
690
- 'content': collected_content
691
- }])
 
 
692
  yield [
693
  collected_content,
694
  _history,
@@ -697,31 +844,32 @@ class Demo:
697
  gr.update(open=True)
698
  ]
699
  else:
700
- raise ValueError("No content was generated from either API")
701
  except Exception as e:
702
- raise ValueError(f'Error calling APIs: {str(e)}')
703
-
704
  def clear_history(self):
705
  return []
706
 
707
-
708
  ####################################################
709
- # 1) deploy_to_vercel 함수
710
  ####################################################
711
  def deploy_to_vercel(code: str):
712
- print(f"[DEBUG] deploy_to_vercel() 시작. code 길이: {len(code) if code else 0}")
 
713
  try:
714
  if not code or len(code.strip()) < 10:
715
- print("[DEBUG] 배포 불가: code 짧음")
716
  return "No code to deploy."
717
 
 
718
  token = "A8IFZmgW2cqA4yUNlLPnci0N"
719
  if not token:
720
- print("[DEBUG] Vercel 토큰이 없음.")
721
  return "Vercel token is not set."
722
 
723
  project_name = ''.join(random.choice(string.ascii_lowercase) for _ in range(6))
724
- print(f"[DEBUG] 생성된 project_name: {project_name}")
725
 
726
  deploy_url = "https://api.vercel.com/v13/deployments"
727
  headers = {
@@ -759,16 +907,16 @@ def deploy_to_vercel(code: str):
759
  "projectSettings": project_settings
760
  }
761
 
762
- print("[DEBUG] Vercel API 요청 전송중...")
763
  deploy_response = requests.post(deploy_url, headers=headers, json=deploy_data)
764
- print("[DEBUG] 응답 status_code:", deploy_response.status_code)
765
 
766
  if deploy_response.status_code != 200:
767
- print("[DEBUG] 배포 실패:", deploy_response.text)
768
  return f"Deployment failed: {deploy_response.text}"
769
 
770
  deployment_url = f"https://{project_name}.vercel.app"
771
- print(f"[DEBUG] 배포 성공 -> URL: {deployment_url}")
772
  time.sleep(5)
773
 
774
  return (
@@ -777,55 +925,42 @@ def deploy_to_vercel(code: str):
777
  f"[**{deployment_url}**]({deployment_url})"
778
  )
779
 
780
-
781
-
782
  except Exception as e:
783
- print("[ERROR] deploy_to_vercel() 예외:", e)
784
  return f"Error during deployment: {str(e)}"
785
 
786
 
787
-
788
  # ------------------------
789
- # (3) handle_deploy_legacy
790
  # ------------------------
791
  def handle_deploy_legacy(code):
792
- logger.debug(f"[handle_deploy_legacy] code 길이: {len(code) if code else 0}")
793
  if not code or len(code.strip()) < 10:
794
- logger.info("[handle_deploy_legacy] 코드가 짧음.")
795
- return "<div style='color:red;'>배포할 코드가 없습니다.</div>"
796
 
797
- # 1) 코드 블록 제거
798
  clean_code = remove_code_block(code)
799
-
800
- # 2) Vercel에 배포
801
  result = deploy_to_vercel(clean_code)
802
- logger.debug(f"[handle_deploy_legacy] deploy_to_vercel 결과: {result}")
803
-
804
- # 3) 배포 URL 추출
805
- import re
806
 
807
  match = re.search(r'https?://[\w.-]+\.vercel\.app', result)
808
  if match:
809
  deployment_url = match.group(0)
810
- # 4) iframe으로 직접 렌더링
811
  iframe_html = (
812
  f'<iframe src="{deployment_url}" '
813
  'width="100%" height="600px" style="border:none;" '
814
  'sandbox="allow-scripts allow-same-origin allow-popups"></iframe>'
815
  )
816
- logger.debug("[handle_deploy_legacy] iframe_html 반환")
817
  return iframe_html
818
 
819
- # 5) URL 못 찾으면 오류 메시지
820
- logger.warning("[handle_deploy_legacy] 배포 URL을 찾을 수 없음")
821
  safe_result = html.escape(result)
822
- return f"<div style='color:red;'>배포 URL 찾을 수 없습니다.<br>결과: {safe_result}</div>"
823
-
824
-
825
 
826
 
827
  # ------------------------
828
- # 8) Gradio / Modelscope UI 빌드
829
  # ------------------------
830
  demo_instance = Demo()
831
  theme = gr.themes.Soft(
@@ -838,102 +973,85 @@ theme = gr.themes.Soft(
838
  )
839
 
840
  with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
841
- header_html = gr.HTML("""
842
  <style>
843
  .app-header{ text-align:center; margin-bottom:24px; }
844
- .badge-row{
845
- display:inline-flex; /* 가로 정렬 */
846
- gap:8px; /* 배지 사이 여백 */
847
- margin:8px 0;
848
- }
849
  </style>
850
-
851
  <div class="app-header">
852
  <h1>🎮 Vibe Game Craft</h1>
853
-
854
  <div class="badge-row">
855
- <!-- ① Vibe-Game 공간 배지 -->
856
  <a href="https://huggingface.co/spaces/openfree/Vibe-Game" target="_blank">
857
  <img src="https://img.shields.io/static/v1?label=huggingface&message=Vibe%20Game%20Craft&color=%23800080&labelColor=%23ffa500&logo=huggingface&logoColor=%23ffff00&style=for-the-badge" alt="HF Vibe badge">
858
  </a>
859
-
860
- <!-- ② Game Gallery 배지 -->
861
  <a href="https://huggingface.co/spaces/openfree/Game-Gallery" target="_blank">
862
  <img src="https://img.shields.io/static/v1?label=huggingface&message=Game%20Gallery&color=%23800080&labelColor=%23ffa500&logo=huggingface&logoColor=%23ffff00&style=for-the-badge" alt="HF Gallery badge">
863
  </a>
864
-
865
- <!-- ③ Discord 배지 -->
866
  <a href="https://discord.gg/openfreeai" target="_blank">
867
  <img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=%230000ff&labelColor=%23800080&logo=discord&logoColor=white&style=for-the-badge" alt="Discord badge">
868
  </a>
869
  </div>
870
-
871
- <p>프롬프트 입력만으로 최신 LLM들과 Agent가 협업하여 웹 기반 HTML5, JavaScript, CSS 게임을 생성합니다. 실시간 미리보기와 배포 기능도 지원됩니다.</p>
872
  </div>
873
  """)
874
-
875
-
876
 
877
  history = gr.State([])
878
  setting = gr.State({"system": SystemPrompt})
879
- deploy_status = gr.State({"is_deployed": False,"status": "","url": "","message": ""})
880
 
881
  with ms.Application() as app:
882
  with antd.ConfigProvider():
883
-
884
- with antd.Drawer(open=False, title="코드 보기", placement="left", width="750px") as code_drawer:
885
  code_output = legacy.Markdown()
886
 
887
- with antd.Drawer(open=False, title="히스토리", placement="left", width="900px") as history_drawer:
888
  history_output = legacy.Chatbot(show_label=False, flushing=False, height=960, elem_classes="history_chatbot")
889
 
890
  with antd.Drawer(
891
  open=False,
892
- title="게임 템플릿",
893
  placement="right",
894
  width="900px",
895
  elem_classes="session-drawer"
896
  ) as session_drawer:
897
  with antd.Flex(vertical=True, gap="middle"):
898
- gr.Markdown("### 사용 가능한 게임 템플릿")
899
  session_history = gr.HTML(elem_classes="session-history")
900
- close_btn = antd.Button("닫기", type="default", elem_classes="close-btn")
901
-
902
- with antd.Row(gutter=[32, 12], align="top", elem_classes="equal-height-container") as layout:
903
 
904
- # 왼쪽 Col
 
905
  with antd.Col(span=24, md=16, elem_classes="equal-height-col"):
906
  with ms.Div(elem_classes="right_panel panel"):
907
- gr.HTML(r"""
908
  <div class="render_header">
909
  <span class="header_btn"></span><span class="header_btn"></span><span class="header_btn"></span>
910
  </div>
911
  """)
912
  with antd.Tabs(active_key="empty", render_tab_bar="() => null") as state_tab:
913
  with antd.Tabs.Item(key="empty"):
914
- empty = antd.Empty(description="게임을 만들려면 설명을 입력하세요", elem_classes="right_content")
915
  with antd.Tabs.Item(key="loading"):
916
- loading = antd.Spin(True, tip="게임 코드 생성 중...", size="large", elem_classes="right_content")
917
  with antd.Tabs.Item(key="render"):
918
  sandbox = gr.HTML(elem_classes="html_content")
919
 
920
- # 오른쪽 Col
921
  with antd.Col(span=24, md=8, elem_classes="equal-height-col"):
922
  with antd.Flex(vertical=True, gap="small", elem_classes="right-top-buttons"):
923
  with antd.Flex(gap="small", elem_classes="setting-buttons", justify="space-between"):
924
- codeBtn = antd.Button("🧑‍💻 코드 보기", type="default", elem_classes="code-btn")
925
- historyBtn = antd.Button("📜 히스토리", type="default", elem_classes="history-btn")
926
- template_btn = antd.Button("🎮 템플릿", type="default", elem_classes="template-btn")
927
-
928
  with antd.Flex(gap="small", justify="space-between", elem_classes="action-buttons"):
929
- btn = antd.Button("전송", type="primary", size="large", elem_classes="send-btn")
930
- boost_btn = antd.Button("증강", type="default", size="large", elem_classes="boost-btn")
931
- execute_btn = antd.Button("코드", type="default", size="large", elem_classes="execute-btn")
932
- deploy_btn = antd.Button("배포", type="default", size="large", elem_classes="deploy-btn")
933
- clear_btn = antd.Button("클리어", type="default", size="large", elem_classes="clear-btn")
934
 
935
  with antd.Flex(vertical=True, gap="middle", wrap=True, elem_classes="input-panel"):
936
- # ── 배포 결과 메시지가 가장 위에 오도록 ──
937
  deploy_result_container = gr.Markdown(value="", visible=False)
938
  input_text = antd.InputTextarea(
939
  size="large",
@@ -941,19 +1059,15 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
941
  placeholder=random.choice(DEMO_LIST)['description'],
942
  max_length=100000
943
  )
944
- gr.HTML('<div class="help-text">💡 원하는 게임의 설명을 입력하세요. 예: "테트리스 게임 제작해줘."</div>')
945
-
946
 
947
-
948
- # Code Drawer 열기/닫기
949
  codeBtn.click(lambda: gr.update(open=True), inputs=[], outputs=[code_drawer])
950
  code_drawer.close(lambda: gr.update(open=False), inputs=[], outputs=[code_drawer])
951
 
952
- # History Drawer 열기/닫기
953
  historyBtn.click(history_render, inputs=[history], outputs=[history_drawer, history_output])
954
  history_drawer.close(lambda: gr.update(open=False), inputs=[], outputs=[history_drawer])
955
 
956
- # Template Drawer
957
  template_btn.click(
958
  fn=lambda: (gr.update(open=True), load_all_templates()),
959
  outputs=[session_drawer, session_history],
@@ -962,42 +1076,29 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
962
  session_drawer.close(lambda: (gr.update(open=False), gr.HTML("")), outputs=[session_drawer, session_history])
963
  close_btn.click(lambda: (gr.update(open=False), gr.HTML("")), outputs=[session_drawer, session_history])
964
 
965
- # 전송 버튼
966
- btn.click(
967
  demo_instance.generation_code,
968
  inputs=[input_text, setting, history],
969
  outputs=[code_output, history, sandbox, state_tab, code_drawer]
970
  )
971
 
972
- # 클리어 버튼
973
  clear_btn.click(demo_instance.clear_history, inputs=[], outputs=[history])
974
 
975
- # 증강 버튼
976
- boost_btn.click(
977
- fn=handle_boost,
978
- inputs=[input_text],
979
- outputs=[input_text, state_tab]
980
- )
981
-
982
- # 코드 실행 버튼
983
- execute_btn.click(
984
- fn=execute_code,
985
- inputs=[input_text],
986
- outputs=[sandbox, state_tab]
987
- )
988
 
989
  deploy_btn.click(
990
- fn=deploy_and_show, # ← 반드시 이 함수
991
  inputs=[code_output],
992
  outputs=[
993
- deploy_result_container, # 오른쪽 메시지
994
- sandbox, # 왼쪽 iframe
995
- state_tab # 탭 전환
996
  ]
997
- )
998
- # ------------------------
999
- # 9) 실행
1000
- # ------------------------
1001
  if __name__ == "__main__":
1002
  try:
1003
  demo_instance = Demo()
 
8
  import asyncio
9
  import requests
10
  import anthropic
 
11
  import io
12
  import logging
13
 
 
20
  import modelscope_studio.components.legacy as legacy
21
  import modelscope_studio.components.antd as antd
22
 
23
+ # === [1] Logger Setup ===
24
  log_stream = io.StringIO()
25
  handler = logging.StreamHandler(log_stream)
26
  logger = logging.getLogger()
27
+ logger.setLevel(logging.DEBUG) # Set desired level
28
  logger.addHandler(handler)
29
 
30
  def get_logs():
31
+ """Return the logs in the StringIO buffer as a string."""
32
  return log_stream.getvalue()
33
 
34
  import re
35
 
36
+ deploying_flag = False # Global flag for deployment
37
 
38
 
39
  def get_deployment_update(code_md: str):
40
+ """Return a Gradio Markdown update with the Vercel deployment status."""
41
  clean = remove_code_block(code_md)
42
  result = deploy_to_vercel(clean)
43
 
44
  m = re.search(r"https?://[\w\.-]+\.vercel\.app", result)
45
+ if m:
46
  url = m.group(0)
47
  md = (
48
+ "✅ **Deployment Complete!**\n\n"
49
+ f"➡️ [Open Deployed App]({url})"
50
  )
51
+ else:
52
  md = (
53
+ "❌ **Deployment Failed**\n\n"
54
  f"```\n{result}\n```"
55
  )
56
 
 
57
  return gr.update(value=md, visible=True)
58
 
59
 
60
  # ------------------------
61
+ # 1) DEMO_LIST and SystemPrompt
62
  # ------------------------
63
 
 
64
  DEMO_LIST = [
65
+ {
66
+ "description": (
67
+ "Please build a classic Tetris game with blocks falling from the top. "
68
+ "Use arrow keys to move and rotate blocks. A completed horizontal line clears "
69
+ "and increases the score. Speed up over time, include a game over condition, and display score."
70
+ )
71
+ },
72
+ {
73
+ "description": (
74
+ "Please build a two-player chess game where players take turns. Implement basic chess rules "
75
+ "(King, Queen, Rook, Bishop, Knight, Pawn moves) with check/checkmate detection. "
76
+ "Enable drag-and-drop for moving pieces and record each move."
77
+ )
78
+ },
79
+ {
80
+ "description": (
81
+ "Please build a memory matching card game. Flip a card to reveal an image; "
82
+ "if two match, the player scores. Include flip animation, track attempts, and implement difficulty levels (easy/medium/hard)."
83
+ )
84
+ },
85
+ {
86
+ "description": (
87
+ "Please build a space shooter game. Control a spaceship with arrow keys and fire with the spacebar. "
88
+ "Waves of enemies attack, collisions are detected, and power-ups (shield, multi-fire, speed boost) appear. Difficulty increases gradually."
89
+ )
90
+ },
91
+ {
92
+ "description": (
93
+ "Please build a sliding puzzle (3x3 or 4x4). Shuffle tiles, then slide them into place using the empty space. "
94
+ "Include a shuffle function, move counter, completion message, and optional size selection for difficulty."
95
+ )
96
+ },
97
+ {
98
+ "description": (
99
+ "Please build a classic Snake game. Control the snake with arrow keys to eat randomly placed food. "
100
+ "Eating food grows the snake. Colliding with itself or a wall ends the game. Score equals the number of food pieces eaten. "
101
+ "Increase speed over time."
102
+ )
103
+ },
104
+ {
105
+ "description": (
106
+ "Please build a Breakout game. Move a paddle at the bottom to bounce a ball and break bricks at the top. "
107
+ "Clearing all bricks finishes a level, losing the ball reduces a life. The ball speeds up over time, "
108
+ "and special bricks can grant power-ups."
109
+ )
110
+ },
111
+ {
112
+ "description": (
113
+ "Please build a tower defense game. Place various towers (basic, splash, slow) to stop enemies traveling along a path. "
114
+ "Each wave is stronger, and destroying enemies grants resources to build or upgrade towers."
115
+ )
116
+ },
117
+ {
118
+ "description": (
119
+ "Please build an endless runner. The player jumps over obstacles (rocks, pits, etc.) with spacebar or mouse click. "
120
+ "Distance is the score. Include collectible coins, power-ups, and increase speed over time."
121
+ )
122
+ },
123
+ {
124
+ "description": (
125
+ "Please build a 2D platformer. Move with arrow keys, jump with spacebar, and collect items. "
126
+ "Avoid enemies/traps and reach the goal. Implement multiple levels with a simple health system and checkpoints."
127
+ )
128
+ },
129
+ {
130
+ "description": (
131
+ "Please build a maze game that generates a new maze each time. "
132
+ "Use arrow keys to move from the start to the exit. Generate mazes using an algorithm (like DFS or Prim's). "
133
+ "Add a timer and optionally show a shortest path hint."
134
+ )
135
+ },
136
+ {
137
+ "description": (
138
+ "Please build a simple turn-based RPG. The player moves on a tile-based map, encountering monsters triggers turn-based combat. "
139
+ "Include basic attacks, special skills, items, and experience/level-up. Add a shop to buy equipment after winning battles."
140
+ )
141
+ },
142
+ {
143
+ "description": (
144
+ "Please build a match-3 puzzle game. Swap adjacent items to match 3 or more. "
145
+ "Matched items disappear, scoring points. Larger matches create special items; chain combos yield extra points. "
146
+ "Include a target score or limited moves/time mode."
147
+ )
148
+ },
149
+ {
150
+ "description": (
151
+ "Please build a Flappy Bird-style game. The bird jumps with spacebar or mouse click, avoiding top/bottom pipes. "
152
+ "Each pipe pair passed scores 1 point, and hitting a pipe or the top/bottom ends the game. Store high scores locally."
153
+ )
154
+ },
155
+ {
156
+ "description": (
157
+ "Please build a spot-the-difference game using pairs of similar images with 5-10 differences. "
158
+ "Click differences to mark them. There's a time limit, with penalty for wrong clicks. "
159
+ "Include a hint system and easy/hard difficulty modes."
160
+ )
161
+ },
162
+ {
163
+ "description": (
164
+ "Please build a typing game where words fall from the top. Type them correctly before they reach the bottom. "
165
+ "Words vary in length and speed based on difficulty. Special words provide bonuses or extra time. "
166
+ "Difficulty increases gradually with the score."
167
+ )
168
+ },
169
+ {
170
+ "description": (
171
+ "Please build a mini golf game with a basic physics engine. Drag to set shot direction and power, then shoot to reach the hole. "
172
+ "Multiple courses with obstacles (sand, water, ramps). Keep track of strokes and total score. Optional wind and shot preview."
173
+ )
174
+ },
175
+ {
176
+ "description": (
177
+ "Please build a fishing simulator. Click to cast, and if a fish bites, a timing mini-game determines success. "
178
+ "Different fish have different rarity and scores. Earn gold for upgrades (rods, bait). "
179
+ "Time/weather can affect which fish appear."
180
+ )
181
+ },
182
+ {
183
+ "description": (
184
+ "Please build a single-player or AI-versus bingo game. A 5x5 grid with numbers 1-25 is randomized. "
185
+ "Players mark chosen numbers in turns. A line (horizontal, vertical, diagonal) is a 'bingo'. "
186
+ "Get 3 bingos first to win. Include a timer and record wins/losses."
187
+ )
188
+ },
189
+ {
190
+ "description": (
191
+ "Please build a rhythm game with notes rising from the bottom. Press keys (D,F,J,K) at the right time. "
192
+ "Judge timing as Perfect, Good, or Miss. Show combos and final results (accuracy, combos, score). Include difficulty (note speed)."
193
+ )
194
+ },
195
+ {
196
+ "description": (
197
+ "Please build a top-down 2D racing game. The player drives a car with arrow keys on a track. "
198
+ "Leaving the track slows the car. Add AI opponents, a 3-lap race mode, and a time attack mode. "
199
+ "Offer multiple cars with different speeds/handling."
200
+ )
201
+ },
202
+ {
203
+ "description": (
204
+ "Please build a trivia quiz game with various categories. 4 multiple-choice answers, 30s limit per question. "
205
+ "Correct answers score points, wrong answers cost lives. Provide difficulty levels and hint items. "
206
+ "Show a summary of results at the end."
207
+ )
208
+ },
209
+ {
210
+ "description": (
211
+ "Please build a shooting gallery with moving targets. Click to shoot. Targets move at various speeds/patterns. "
212
+ "Limited time and ammo. Special targets grant bonuses. Consecutive hits form combos. Increase target speed for difficulty."
213
+ )
214
+ },
215
+ {
216
+ "description": (
217
+ "Please build a board game with a virtual dice (1-6) to move a piece around. Different board spaces trigger events: "
218
+ "move forward/back, skip turns, mini-games, etc. Players collect items to use. Up to 4 players or AI. "
219
+ "First to reach the goal or highest points wins."
220
+ )
221
+ },
222
+ {
223
+ "description": (
224
+ "Please build a top-down zombie survival game. Move with WASD, aim and shoot with the mouse. "
225
+ "Zombies come in waves, increasing in number and speed. Implement ammo, health packs, bombs, and special zombie types. "
226
+ "Survive as long as possible."
227
+ )
228
+ },
229
+ {
230
+ "description": (
231
+ "Please build a soccer penalty shootout game. The player sets direction and power to shoot, or chooses a direction for the goalkeeper. "
232
+ "5 rounds each, plus sudden death if tied. The AI goalkeeper can learn player tendencies. Support single-player and 2-player local mode."
233
+ )
234
+ },
235
+ {
236
+ "description": (
237
+ "Please build a classic Minesweeper game. NxN grid, M mines. Left-click reveals a cell, right-click flags a mine. "
238
+ "Numbers show how many mines are adjacent. Reveal all safe cells to win; hitting a mine loses. "
239
+ "Offer beginner/intermediate/expert sizes and ensure the first click is safe."
240
+ )
241
+ },
242
+ {
243
+ "description": (
244
+ "Please build a Connect Four game on a 7x6 grid. Players drop colored discs to form a line of 4 horizontally, vertically, or diagonally. "
245
+ "Alternate turns. First to connect 4 wins, or it's a draw if the board fills. Include AI and local 2-player modes."
246
+ )
247
+ },
248
+ {
249
+ "description": (
250
+ "Please build a Scrabble-style word game. Each player has 7 letter tiles. Place them on the board to form words. "
251
+ "All new words must connect to existing ones. Each tile has a point value. Include bonus spaces (double letter, triple word). "
252
+ "Validate words against a dictionary, support 1-4 players or AI."
253
+ )
254
+ },
255
+ {
256
+ "description": (
257
+ "Please build a 2D tank battle game. Move with WASD, aim and fire with the mouse. "
258
+ "Destructible terrain (brick, wood) and indestructible blocks (steel, water). Various weapons (shell, spread, laser) and power-ups. "
259
+ "Add multiple stages with AI enemies and increasing difficulty."
260
+ )
261
+ },
262
+ {
263
+ "description": (
264
+ "Please build a gem-matching puzzle game. Swap adjacent gems to match 3. Matched gems vanish and new gems fall. "
265
+ "4+ matches create special gems with bigger explosions. Chain combos increase score. "
266
+ "Use limited moves or time, plus special objectives like clearing obstacles."
267
+ )
268
+ },
269
+ {
270
+ "description": (
271
+ "Please build a single-tower defense game. The tower is in the center and auto-attacks incoming enemies. "
272
+ "Between waves, use earned resources to upgrade damage, speed, or range. "
273
+ "Waves get harder with different enemy types (fast, armored, splitting). Survive as long as possible."
274
+ )
275
+ },
276
+ {
277
+ "description": (
278
+ "Please build a side-scrolling runner with zombies and obstacles. Press space to jump and S to slide. "
279
+ "Collect coins and power-ups (invincibility, magnet, slow). Occasionally fight a mini-boss zombie. "
280
+ "Earn points for distance, spend coins on upgrades like double-jump or extra health."
281
+ )
282
+ },
283
+ {
284
+ "description": (
285
+ "Please build a top-down action RPG. Move with WASD, basic attack with the mouse, and use skills (keys 1-4). "
286
+ "Defeat monsters to gain XP and items, level up to improve stats. Equip weapons/armor, use a skill tree, and fight bosses. "
287
+ "Add simple quests and multiple zones."
288
+ )
289
+ },
290
  ]
291
 
 
292
  SystemPrompt = """
293
+ # GameCraft System Prompt
294
+
295
+ ## 1. Basic Info & Role
296
+ Your name is 'GameCraft'. You are a web game developer specialized in gameplay mechanics, interactive design, and performance optimization.
297
+ You build concise, efficient HTML/JS/CSS web-based games.
298
+
299
+ ## 2. Core Tech Stack
300
+ - **Frontend**: HTML5, CSS3, JavaScript (ES6+)
301
+ - **Rendering**: Directly in browser
302
+ - **Code Style**: Vanilla JS first, minimal external libraries
303
+
304
+ ## 3. Game Type Guidelines
305
+ ### 3.1 Arcade/Action
306
+ - Simple collision detection
307
+ - Keyboard/touch optimization
308
+ - Basic scoring
309
+
310
+ ### 3.2 Puzzle
311
+ - Clear rules & win conditions
312
+ - Basic difficulty levels
313
+ - Focus on core mechanics
314
+
315
+ ### 3.3 Card/Board
316
+ - Simplified turn-based system
317
+ - Automate main rules
318
+ - Focus on core logic
319
+
320
+ ### 3.4 Simulation
321
+ - Efficient state management
322
+ - Implement essential interactions
323
+ - Only critical elements
324
+
325
+ ## 4. Emoji Usage 🎮
326
+ - Use emojis for key UI elements (HP ❤️, coin 💰, timer ⏱️)
327
+ - Minimal, essential usage
328
+
329
+ ## 5. Technical Implementation
330
+ ### 5.1 Code Structure
331
+ - **Conciseness**: Keep code as short as possible, minimal comments
332
+ - **Modularity**: Split by function, avoid unnecessary abstraction
333
+ - **Optimization**: Focus on game loop & rendering
334
+ - **Size Limit**: Entire code under 200 lines
335
+
336
+ ### 5.2 Performance
337
+ - Minimize DOM manipulation
338
+ - Remove unused variables
339
+ - Watch memory usage
340
+
341
+ ### 5.3 Responsive
342
+ - Basic responsive layout
343
+ - Simple UI focusing on core features
344
+
345
+ ## 6. External Libraries
346
+ - Use minimal libraries, only if necessary
347
+ - If used, load via CDN
348
+
349
+ ## 7. Accessibility & Inclusion
350
+ - Only essential accessibility features
351
+
352
+ ## 8. Constraints & Notes
353
+ - No external API calls
354
+ - Keep code minimal (<200 lines)
355
+ - Minimal comments (only what's necessary)
356
+ - Avoid unnecessary features
357
+
358
+ ## 9. Output Format
359
+ - Provide code in an HTML code block only
360
+ - No extra explanations, just a single-file ready code
361
+ - Must be fully self-contained
362
+
363
+ ## 10. Code Quality
364
+ - Efficiency & brevity are top priorities
365
+ - Focus on core gameplay mechanics
366
+ - Prefer basic functionality over complexity
367
+ - No redundant comments
368
+ - Single file, <200 lines total
369
+
370
+ ## 11. Important: Code Generation Limit
371
+ - Must NOT exceed 200 lines
372
+ - Omit non-essential details
373
+ - If code grows too long, simplify or remove features
 
374
  """
375
 
 
376
  # ------------------------
377
+ # 2) Constants, Functions, Classes
378
  # ------------------------
379
 
380
  class Role:
 
388
  IMAGE_CACHE = {}
389
 
390
  def get_image_base64(image_path):
391
+ """Read an image file into base64 and cache it."""
 
 
392
  if image_path in IMAGE_CACHE:
393
  return IMAGE_CACHE[image_path]
394
  try:
 
415
 
416
 
417
  # ------------------------
418
+ # 3) API Setup
419
  # ------------------------
420
 
421
  YOUR_ANTHROPIC_TOKEN = os.getenv('ANTHROPIC_API_KEY', '').strip()
 
 
422
  claude_client = anthropic.Anthropic(api_key=YOUR_ANTHROPIC_TOKEN)
 
423
 
424
  async def try_claude_api(system_message, claude_messages, timeout=15):
425
  """
426
+ Claude 3.7 Sonnet API call (streaming).
427
  """
428
  try:
429
+ system_message_with_limit = (
430
+ system_message
431
+ + "\n\nAdditional note: The generated code must NOT exceed 200 lines. Keep it concise, minimal comments, essential only."
432
+ )
433
  start_time = time.time()
434
+
435
  with claude_client.messages.stream(
436
  model="claude-3-7-sonnet-20250219",
437
  max_tokens=19800,
438
  system=system_message_with_limit,
439
  messages=claude_messages,
440
+ temperature=0.3
441
  ) as stream:
442
  collected_content = ""
443
  for chunk in stream:
 
452
  except Exception as e:
453
  raise e
454
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
455
 
456
  # ------------------------
457
+ # 4) Templates
458
  # ------------------------
459
 
460
  def load_json_data():
461
  data_list = []
462
  for item in DEMO_LIST:
463
  data_list.append({
464
+ "name": f"[Game] {item['description'][:25]}...",
465
  "prompt": item['description']
466
  })
467
  return data_list
468
 
469
  def create_template_html(title, items):
470
  """
471
+ Create simple HTML for game templates (no images).
472
  """
473
  html_content = r"""
474
  <style>
 
523
  """
524
  html_content += card_html
525
  html_content += r"""
526
+ </div>
527
  <script>
528
  function copyToInput(card) {
529
  const prompt = card.dataset.prompt;
 
531
  if (textarea) {
532
  textarea.value = prompt;
533
  textarea.dispatchEvent(new Event('input', { bubbles: true }));
 
534
  document.querySelector('.session-drawer .close-btn').click();
535
  }
536
  }
537
  </script>
 
538
  """
539
  return gr.HTML(value=html_content)
540
 
541
  def load_all_templates():
542
+ return create_template_html("Available Game Templates", load_json_data())
543
 
544
 
545
  # ------------------------
546
+ # 5) Deploy/Boost/Utils
547
  # ------------------------
548
 
549
  def remove_code_block(text):
 
562
  return text.strip()
563
 
564
  def optimize_code(code: str) -> str:
565
+ """Remove comments/whitespace if code exceeds 200 lines."""
566
  if not code or len(code.strip()) == 0:
567
  return code
568
 
 
570
  if len(lines) <= 200:
571
  return code
572
 
573
+ # Remove comments
574
  comment_patterns = [
575
  r'/\*[\s\S]*?\*/',
576
  r'//.*?$',
 
580
  for pattern in comment_patterns:
581
  cleaned_code = re.sub(pattern, '', cleaned_code, flags=re.MULTILINE)
582
 
583
+ # Remove extra blank lines
584
  cleaned_lines = []
585
  empty_line_count = 0
586
  for line in cleaned_code.split('\n'):
 
593
  cleaned_lines.append(line)
594
 
595
  cleaned_code = '\n'.join(cleaned_lines)
596
+ # Remove console logs
597
  cleaned_code = re.sub(r'console\.log\(.*?\);', '', cleaned_code, flags=re.MULTILINE)
598
+ # Remove double spaces
599
  cleaned_code = re.sub(r' {2,}', ' ', cleaned_code)
600
  return cleaned_code
601
 
 
625
  return f'<iframe src="{data_uri}" width="100%" height="920px" style="border:none;"></iframe>'
626
 
627
  def boost_prompt(prompt: str) -> str:
628
+ """
629
+ Ask Claude to refine the prompt for clarity and conciseness,
630
+ ensuring minimal extraneous requirements.
631
+ """
632
  if not prompt:
633
  return ""
634
+ boost_system_prompt = (
635
+ "You are an expert prompt engineer for web game development. "
636
+ "Please analyze the given prompt and refine it for clarity and brevity, "
637
+ "while preserving the original intent. Focus on: \n"
638
+ "1) Clarifying core gameplay mechanics\n"
639
+ "2) Essential interactions only\n"
640
+ "3) Simple UI elements\n"
641
+ "4) Minimizing features to ensure code under 600 lines\n"
642
+ "5) Basic rules and win/lose conditions\n\n"
643
+ "Important: Exclude unneeded details, keep it under 600 lines of code overall, and be concise."
644
+ )
 
 
 
 
 
645
  try:
646
+ # Single direct request to Claude
647
+ response = claude_client.messages.create(
648
+ model="claude-3-7-sonnet-20250219",
649
+ max_tokens=10000,
650
+ temperature=0.3,
651
+ messages=[
652
+ {"role": "user", "content": f"Refine this game prompt while keeping it concise: {prompt}"}
653
+ ],
654
+ system=boost_system_prompt
655
+ )
656
+ if hasattr(response, 'content') and len(response.content) > 0:
657
+ return response.content[0].text
658
+ return prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659
  except Exception:
660
  return prompt
661
 
 
670
  return gr.update(open=True), history
671
 
672
  def execute_code(query: str):
673
+ """Try to interpret the content as code and display it."""
674
  if not query or query.strip() == '':
675
  return None, gr.update(active_key="empty")
676
  try:
 
679
  clean_code = clean_code[7:].strip()
680
  if clean_code.endswith('```'):
681
  clean_code = clean_code[:-3].strip()
682
+
683
+ if not (clean_code.strip().startswith('<!DOCTYPE') or clean_code.strip().startswith('<html')):
684
  if not ('<body' in clean_code and '</body>' in clean_code):
685
+ clean_code = (
686
+ "<!DOCTYPE html>\n<html>\n<head>\n"
687
+ ' <meta charset="UTF-8">\n'
688
+ ' <meta name="viewport" content="width=device-width, initial-scale=1.0">\n'
689
+ ' <title>Game Preview</title>\n'
690
+ "</head>\n<body>\n"
691
+ + clean_code
692
+ + "\n</body>\n</html>"
693
+ )
 
 
694
  return send_to_sandbox(clean_code), gr.update(active_key="render")
695
  except Exception as e:
696
  print(f"Execute code error: {str(e)}")
 
698
 
699
 
700
  # ------------------------
701
+ # 6) Demo Class
702
  # ------------------------
703
+
704
  def deploy_and_show(code_md: str):
705
+ """
706
+ Deploy button logic.
707
+ """
708
  global deploying_flag
709
 
710
+ # Prevent repeated clicks
711
  if deploying_flag:
712
+ return (
713
+ gr.update(value="⏳ Already deploying...", visible=True),
714
+ None,
715
+ gr.update(active_key="empty")
716
+ )
717
  deploying_flag = True
718
 
 
719
  clean = remove_code_block(code_md)
720
  result = deploy_to_vercel(clean)
721
 
722
+ m = re.search(r"https?://[\w\.-]+\.vercel\.app", result)
723
+ if m:
724
+ url = m.group(0)
725
+ md_out = f"✅ **Deployment Complete!**\n\n➡️ [Open]({url})"
726
+ iframe = (
727
+ f"<iframe src='{url}' width='100%' height='920px' style='border:none;'></iframe>"
728
+ )
 
 
 
729
  deploying_flag = False
730
+ return (
731
+ gr.update(value=md_out, visible=True),
732
+ iframe,
733
+ gr.update(active_key="render")
734
+ )
735
 
736
+ md_err = f"❌ **Deployment Failed**\n\n```\n{result}\n```"
 
737
  deploying_flag = False
738
+ return (
739
+ gr.update(value=md_err, visible=True),
740
+ None,
741
+ gr.update(active_key="empty")
742
+ )
743
 
744
  class Demo:
745
  def __init__(self):
746
  pass
747
+
748
+ async def generation_code(
749
+ self,
750
+ query: Optional[str],
751
+ _setting: Dict[str, str],
752
+ _history: Optional[History]
753
+ ):
754
+ """
755
+ Create the game code from user prompt using only Claude.
756
+ """
757
  if not query or query.strip() == '':
758
  query = random.choice(DEMO_LIST)['description']
759
 
760
  if _history is None:
761
  _history = []
762
 
763
+ # Additional constraints for the request
764
+ query = (
765
+ "Please create the following game. Important requirements: \n"
766
+ "1. Keep code as concise as possible.\n"
767
+ "2. Omit unnecessary comments.\n"
768
+ "3. The code must NOT exceed 600 lines.\n"
769
+ "4. All code in one HTML file.\n"
770
+ "5. Implement core features only.\n\n"
771
+ f"Game request: {query}"
772
+ )
773
 
774
  messages = history_to_messages(_history, _setting['system'])
775
  system_message = messages[0]['content']
776
+
777
+ # For Claude
778
  claude_messages = [
779
+ {
780
+ "role": (msg["role"] if msg["role"] != "system" else "user"),
781
+ "content": msg["content"]
782
+ }
783
+ for msg in messages[1:] + [{"role": Role.USER, "content": query}]
784
  if msg["content"].strip() != ''
785
  ]
786
+
 
 
 
 
 
 
 
 
787
  try:
788
+ # Start streaming response
789
  yield [
790
  "Generating code...",
791
  _history,
 
794
  gr.update(open=True)
795
  ]
796
  await asyncio.sleep(0)
797
+
798
  collected_content = None
799
+ async for content in try_claude_api(system_message, claude_messages):
800
+ yield [
801
+ content,
802
+ _history,
803
+ None,
804
+ gr.update(active_key="loading"),
805
+ gr.update(open=True)
806
+ ]
807
+ await asyncio.sleep(0)
808
+ collected_content = content
 
 
 
 
 
 
 
 
 
 
 
 
809
 
810
  if collected_content:
811
  clean_code = remove_code_block(collected_content)
812
  code_lines = clean_code.count('\n') + 1
813
  if code_lines > 700:
814
+ warning_msg = (
815
+ f"⚠️ **Warning: Generated code is too long ({code_lines} lines).**\n"
816
+ "This may cause issues. Try simplifying the request:\n"
817
+ "1) Request a simpler game\n"
818
+ "2) Omit advanced features\n"
819
+ "```html\n"
820
+ + clean_code[:2000]
821
+ + "\n... (truncated) ..."
822
+ )
 
823
  yield [
824
+ warning_msg,
825
  _history,
826
  None,
827
  gr.update(active_key="empty"),
828
  gr.update(open=True)
829
  ]
830
  else:
831
+ # Add final message to chat history
832
+ final_msg = {
833
+ "role": Role.ASSISTANT,
834
+ "content": collected_content
835
+ }
836
+ updated_messages = messages + [final_msg]
837
+ _history = messages_to_history(updated_messages)
838
+
839
  yield [
840
  collected_content,
841
  _history,
 
844
  gr.update(open=True)
845
  ]
846
  else:
847
+ raise ValueError("No content generated from Claude.")
848
  except Exception as e:
849
+ raise ValueError(f"Error calling Claude: {str(e)}")
850
+
851
  def clear_history(self):
852
  return []
853
 
 
854
  ####################################################
855
+ # 1) deploy_to_vercel
856
  ####################################################
857
  def deploy_to_vercel(code: str):
858
+ """Deploy code to Vercel and return the response."""
859
+ print(f"[DEBUG] deploy_to_vercel() start. code length: {len(code) if code else 0}")
860
  try:
861
  if not code or len(code.strip()) < 10:
862
+ print("[DEBUG] No code to deploy (too short).")
863
  return "No code to deploy."
864
 
865
+ # Hard-coded token for demonstration
866
  token = "A8IFZmgW2cqA4yUNlLPnci0N"
867
  if not token:
868
+ print("[DEBUG] Vercel token is not set.")
869
  return "Vercel token is not set."
870
 
871
  project_name = ''.join(random.choice(string.ascii_lowercase) for _ in range(6))
872
+ print(f"[DEBUG] Generated project_name: {project_name}")
873
 
874
  deploy_url = "https://api.vercel.com/v13/deployments"
875
  headers = {
 
907
  "projectSettings": project_settings
908
  }
909
 
910
+ print("[DEBUG] Sending request to Vercel...")
911
  deploy_response = requests.post(deploy_url, headers=headers, json=deploy_data)
912
+ print("[DEBUG] Response status_code:", deploy_response.status_code)
913
 
914
  if deploy_response.status_code != 200:
915
+ print("[DEBUG] Deployment failed:", deploy_response.text)
916
  return f"Deployment failed: {deploy_response.text}"
917
 
918
  deployment_url = f"https://{project_name}.vercel.app"
919
+ print(f"[DEBUG] Deployment success -> URL: {deployment_url}")
920
  time.sleep(5)
921
 
922
  return (
 
925
  f"[**{deployment_url}**]({deployment_url})"
926
  )
927
 
 
 
928
  except Exception as e:
929
+ print("[ERROR] deploy_to_vercel() exception:", e)
930
  return f"Error during deployment: {str(e)}"
931
 
932
 
 
933
  # ------------------------
934
+ # (Legacy Deploy Handler)
935
  # ------------------------
936
  def handle_deploy_legacy(code):
937
+ logger.debug(f"[handle_deploy_legacy] code length: {len(code) if code else 0}")
938
  if not code or len(code.strip()) < 10:
939
+ logger.info("[handle_deploy_legacy] Not enough code.")
940
+ return "<div style='color:red;'>No code to deploy.</div>"
941
 
 
942
  clean_code = remove_code_block(code)
 
 
943
  result = deploy_to_vercel(clean_code)
944
+ logger.debug(f"[handle_deploy_legacy] deploy_to_vercel result: {result}")
 
 
 
945
 
946
  match = re.search(r'https?://[\w.-]+\.vercel\.app', result)
947
  if match:
948
  deployment_url = match.group(0)
 
949
  iframe_html = (
950
  f'<iframe src="{deployment_url}" '
951
  'width="100%" height="600px" style="border:none;" '
952
  'sandbox="allow-scripts allow-same-origin allow-popups"></iframe>'
953
  )
954
+ logger.debug("[handle_deploy_legacy] returning iframe_html")
955
  return iframe_html
956
 
957
+ logger.warning("[handle_deploy_legacy] No deployment URL found.")
 
958
  safe_result = html.escape(result)
959
+ return f"<div style='color:red;'>Deployment URL not found.<br>Result: {safe_result}</div>"
 
 
960
 
961
 
962
  # ------------------------
963
+ # 8) Gradio / Modelscope UI
964
  # ------------------------
965
  demo_instance = Demo()
966
  theme = gr.themes.Soft(
 
973
  )
974
 
975
  with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
976
+ gr.HTML("""
977
  <style>
978
  .app-header{ text-align:center; margin-bottom:24px; }
979
+ .badge-row{ display:inline-flex; gap:8px; margin:8px 0; }
 
 
 
 
980
  </style>
 
981
  <div class="app-header">
982
  <h1>🎮 Vibe Game Craft</h1>
 
983
  <div class="badge-row">
 
984
  <a href="https://huggingface.co/spaces/openfree/Vibe-Game" target="_blank">
985
  <img src="https://img.shields.io/static/v1?label=huggingface&message=Vibe%20Game%20Craft&color=%23800080&labelColor=%23ffa500&logo=huggingface&logoColor=%23ffff00&style=for-the-badge" alt="HF Vibe badge">
986
  </a>
 
 
987
  <a href="https://huggingface.co/spaces/openfree/Game-Gallery" target="_blank">
988
  <img src="https://img.shields.io/static/v1?label=huggingface&message=Game%20Gallery&color=%23800080&labelColor=%23ffa500&logo=huggingface&logoColor=%23ffff00&style=for-the-badge" alt="HF Gallery badge">
989
  </a>
 
 
990
  <a href="https://discord.gg/openfreeai" target="_blank">
991
  <img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=%230000ff&labelColor=%23800080&logo=discord&logoColor=white&style=for-the-badge" alt="Discord badge">
992
  </a>
993
  </div>
994
+ <p>Enter a game description, and this tool will generate an HTML5/JS/CSS game via Claude. Preview or deploy it easily.</p>
 
995
  </div>
996
  """)
 
 
997
 
998
  history = gr.State([])
999
  setting = gr.State({"system": SystemPrompt})
1000
+ deploy_status = gr.State({"is_deployed": False, "status": "", "url": "", "message": ""})
1001
 
1002
  with ms.Application() as app:
1003
  with antd.ConfigProvider():
1004
+ with antd.Drawer(open=False, title="View Code", placement="left", width="750px") as code_drawer:
 
1005
  code_output = legacy.Markdown()
1006
 
1007
+ with antd.Drawer(open=False, title="History", placement="left", width="900px") as history_drawer:
1008
  history_output = legacy.Chatbot(show_label=False, flushing=False, height=960, elem_classes="history_chatbot")
1009
 
1010
  with antd.Drawer(
1011
  open=False,
1012
+ title="Game Templates",
1013
  placement="right",
1014
  width="900px",
1015
  elem_classes="session-drawer"
1016
  ) as session_drawer:
1017
  with antd.Flex(vertical=True, gap="middle"):
1018
+ gr.Markdown("### Available Game Templates")
1019
  session_history = gr.HTML(elem_classes="session-history")
1020
+ close_btn = antd.Button("Close", type="default", elem_classes="close-btn")
 
 
1021
 
1022
+ with antd.Row(gutter=[32, 12], align="top", elem_classes="equal-height-container"):
1023
+ # Left Column
1024
  with antd.Col(span=24, md=16, elem_classes="equal-height-col"):
1025
  with ms.Div(elem_classes="right_panel panel"):
1026
+ gr.HTML("""
1027
  <div class="render_header">
1028
  <span class="header_btn"></span><span class="header_btn"></span><span class="header_btn"></span>
1029
  </div>
1030
  """)
1031
  with antd.Tabs(active_key="empty", render_tab_bar="() => null") as state_tab:
1032
  with antd.Tabs.Item(key="empty"):
1033
+ antd.Empty(description="Enter a description to generate a game", elem_classes="right_content")
1034
  with antd.Tabs.Item(key="loading"):
1035
+ antd.Spin(True, tip="Generating game code...", size="large", elem_classes="right_content")
1036
  with antd.Tabs.Item(key="render"):
1037
  sandbox = gr.HTML(elem_classes="html_content")
1038
 
1039
+ # Right Column
1040
  with antd.Col(span=24, md=8, elem_classes="equal-height-col"):
1041
  with antd.Flex(vertical=True, gap="small", elem_classes="right-top-buttons"):
1042
  with antd.Flex(gap="small", elem_classes="setting-buttons", justify="space-between"):
1043
+ codeBtn = antd.Button("View Code", type="default", elem_classes="code-btn")
1044
+ historyBtn = antd.Button("History", type="default", elem_classes="history-btn")
1045
+ template_btn = antd.Button("🎮 Templates", type="default", elem_classes="template-btn")
1046
+
1047
  with antd.Flex(gap="small", justify="space-between", elem_classes="action-buttons"):
1048
+ send_btn = antd.Button("Send", type="primary", size="large", elem_classes="send-btn")
1049
+ enhance_btn = antd.Button("Enhance", type="default", size="large", elem_classes="boost-btn")
1050
+ code_exec_btn = antd.Button("Code", type="default", size="large", elem_classes="execute-btn")
1051
+ deploy_btn = antd.Button("Deploy", type="default", size="large", elem_classes="deploy-btn")
1052
+ clear_btn = antd.Button("Clear", type="default", size="large", elem_classes="clear-btn")
1053
 
1054
  with antd.Flex(vertical=True, gap="middle", wrap=True, elem_classes="input-panel"):
 
1055
  deploy_result_container = gr.Markdown(value="", visible=False)
1056
  input_text = antd.InputTextarea(
1057
  size="large",
 
1059
  placeholder=random.choice(DEMO_LIST)['description'],
1060
  max_length=100000
1061
  )
1062
+ gr.HTML('<div class="help-text">💡 Describe your game here, e.g. "Please build a simple Tetris game."</div>')
 
1063
 
1064
+ # Drawer Toggles
 
1065
  codeBtn.click(lambda: gr.update(open=True), inputs=[], outputs=[code_drawer])
1066
  code_drawer.close(lambda: gr.update(open=False), inputs=[], outputs=[code_drawer])
1067
 
 
1068
  historyBtn.click(history_render, inputs=[history], outputs=[history_drawer, history_output])
1069
  history_drawer.close(lambda: gr.update(open=False), inputs=[], outputs=[history_drawer])
1070
 
 
1071
  template_btn.click(
1072
  fn=lambda: (gr.update(open=True), load_all_templates()),
1073
  outputs=[session_drawer, session_history],
 
1076
  session_drawer.close(lambda: (gr.update(open=False), gr.HTML("")), outputs=[session_drawer, session_history])
1077
  close_btn.click(lambda: (gr.update(open=False), gr.HTML("")), outputs=[session_drawer, session_history])
1078
 
1079
+ # Buttons
1080
+ send_btn.click(
1081
  demo_instance.generation_code,
1082
  inputs=[input_text, setting, history],
1083
  outputs=[code_output, history, sandbox, state_tab, code_drawer]
1084
  )
1085
 
 
1086
  clear_btn.click(demo_instance.clear_history, inputs=[], outputs=[history])
1087
 
1088
+ enhance_btn.click(handle_boost, inputs=[input_text], outputs=[input_text, state_tab])
1089
+ code_exec_btn.click(execute_code, inputs=[input_text], outputs=[sandbox, state_tab])
 
 
 
 
 
 
 
 
 
 
 
1090
 
1091
  deploy_btn.click(
1092
+ fn=deploy_and_show,
1093
  inputs=[code_output],
1094
  outputs=[
1095
+ deploy_result_container,
1096
+ sandbox,
1097
+ state_tab
1098
  ]
1099
+ )
1100
+
1101
+ # 9) Launch
 
1102
  if __name__ == "__main__":
1103
  try:
1104
  demo_instance = Demo()