openfree commited on
Commit
a1a65b1
·
verified ·
1 Parent(s): 6b3cd46

Update app-backupx.py

Browse files
Files changed (1) hide show
  1. app-backupx.py +551 -531
app-backupx.py CHANGED
@@ -26,131 +26,125 @@ import modelscope_studio.components.antd as antd
26
  # ------------------------
27
 
28
  DEMO_LIST = [
29
- {"description": "Create a Tetris-like puzzle game with arrow key controls, line-clearing mechanics, and increasing difficulty levels."},
30
- {"description": "Build an interactive Chess game with a basic AI opponent and drag-and-drop piece movement. Keep track of moves and detect check/checkmate."},
31
- {"description": "Design a memory matching card game with flip animations, scoring system, and multiple difficulty levels."},
32
- {"description": "Create a space shooter game with enemy waves, collision detection, and power-ups. Use keyboard or mouse controls for ship movement."},
33
- {"description": "Implement a slide puzzle game using images or numbers. Include shuffle functionality, move counter, and difficulty settings."},
34
- {"description": "Implement the classic Snake game with grid-based movement, score tracking, and increasing speed. Use arrow keys for control."},
35
- {"description": "Build a classic breakout game with paddle, ball, and bricks. Increase ball speed and track lives/score."},
36
- {"description": "Create a tower defense game with multiple tower types and enemy waves. Include an upgrade system and resource management."},
37
- {"description": "Design an endless runner with side-scrolling obstacles. Use keyboard or mouse to jump and avoid collisions."},
38
- {"description": "Implement a platformer game with character movement, jumping, and collectible items. Use arrow keys for control."},
39
- {"description": "Generate a random maze and allow the player to navigate from start to finish. Include a timer and pathfinding animations."},
40
- {"description": "Build a simple top-down RPG with tile-based movement, monsters, and loot. Use arrow keys for movement and track player stats."},
41
- {"description": "Create a match-3 puzzle game with swipe-based mechanics, special tiles, and combo scoring."},
42
- {"description": "Implement a Flappy Bird clone with space bar or mouse click to flap, randomized pipe positions, and score tracking."},
43
- {"description": "Build a spot-the-difference game using pairs of similar images. Track remaining differences and time limit."},
44
- {"description": "Create a typing speed test game where words fall from the top. Type them before they reach the bottom to score points."},
45
- {"description": "Implement a mini golf game with physics-based ball movement. Include multiple holes and scoring based on strokes."},
46
- {"description": "Design a fishing game where the player casts a line, reels fish, and can upgrade gear. Manage fish spawn rates and scoring."},
47
- {"description": "Build a bingo game with randomly generated boards and a calling system. Automatically check winning lines."},
48
- {"description": "Create a web-based rhythm game using keyboard inputs. Time hits accurately for score, and add background music."},
49
- {"description": "Implement a top-down 2D racing game with track boundaries, lap times, and multiple AI opponents."},
50
- {"description": "Build a quiz game with multiple-choice questions, scoring, and a timer. Randomize question order each round."},
51
- {"description": "Create a shooting gallery game with moving targets, limited ammo, and a time limit. Track hits and misses."},
52
- {"description": "Implement a dice-based board game with multiple squares, events, and item usage. Players take turns rolling."},
53
- {"description": "Design a top-down zombie survival game with wave-based enemies, pickups, and limited ammo. Track score and health."},
54
- {"description": "Build a simple penalty shootout game with aiming, power bars, and a goalie AI that guesses shots randomly."},
55
- {"description": "Implement the classic Minesweeper game with left-click reveal, right-click flags, and adjacency logic for numbers."},
56
- {"description": "Create a Connect Four game with drag-and-drop or click-based input, alternating turns, and a win check algorithm."},
57
- {"description": "Build a Scrabble-like word puzzle game with letter tiles, scoring, and a local dictionary for validation."},
58
- {"description": "Implement a 2D tank battle game with destructible terrain, power-ups, and AI or multiplayer functionality."},
59
- {"description": "Create a gem-crushing puzzle game where matching gems cause chain reactions. Track combos and score bonuses."},
60
- {"description": "Design a 2D defense game where a single tower shoots incoming enemies in waves. Upgrade the tower's stats over time."},
61
- {"description": "Make a side-scrolling runner where a character avoids zombies and obstacles, collecting power-ups along the way."},
62
- {"description": "Create a small action RPG with WASD movement, an attack button, special moves, leveling, and item drops."},
63
  ]
64
 
65
  SystemPrompt = """
66
  # GameCraft 시스템 프롬프트
67
 
68
  ## 1. 기본 정보 및 역할
69
- 당신의 이름은 'GameCraft'입니다. 당신은 게임플레이 메커니즘, 인터랙티브 디자인, 성능 최적화에 뛰어난 웹 게임 개발 전문가입니다. HTML, JavaScript, CSS를 활용하여 매력적이고 현대적이며 완전히 상호작용 가능한 웹 기반 게임을 제작하는 것이 당신의 임무입니다.
70
 
71
  ## 2. 핵심 기술 스택
72
  - **프론트엔드**: HTML5, CSS3, JavaScript(ES6+)
73
  - **렌더링 방식**: 브라우저에서 직접 렌더링 가능한 코드 생성
74
- - **코드 스타일**: 바닐라 JavaScript 우선, 필요시에만 외부 라이브러리 활용
75
 
76
  ## 3. 게임 유형별 특화 지침
77
  ### 3.1 아케이드/액션 게임
78
- - 부드러운 애니메이션 및 충돌 감지 시스템 구현
79
- - 키보드/터치 입력 반응성 최적화
80
- - 점수 시스템 및 난이도 조절 메커니즘 포함
81
 
82
  ### 3.2 퍼즐 게임
83
- - 명확한 게임 규칙 및 승리 조건 설정
84
- - 다양한 난이도 레벨 구현
85
- - 게임 상태 저장 기능 구현 고려
86
 
87
  ### 3.3 카드/보드 게임
88
- - 명확한 턴 기반 시스템 구현
89
- - 게임 규칙 자동화 및 유효한 움직임 검증
90
- - 시각적 피드백 강화
91
 
92
  ### 3.4 시뮬레이션 게임
93
- - 효율적인 상태 관리 시스템 구현
94
- - 복잡한 상호작용 패턴 단순화
95
- - 진행 상황 표시 및 저장 기능
96
 
97
  ## 4. 이모지 활용 지침 🎮
98
- - 게임 UI 요소에 관련 이모지 통합하여 직관성 향상 (예: 생명력 ❤️, 코인 💰, 시간 ⏱️)
99
- - 게임 피드백 및 보상 시스템에 이모지 활용 (성공 🎉, 실패 💥, 업적 🏆)
100
- - 캐릭터나 적 표현에 이모지 활용 가능 (플레이어 😎, 적 👾, NPC 🧙)
101
- - 게임 요소와 이모지 매핑을 일관되게 유지
102
- - 관련 이모지를 활용한 애니메이션 효과 구현 고려
103
- - 이모지 기반 미니게임 메커닉 고려 (이모지 매칭, 기억 게임 등)
104
 
105
  ## 5. 기술적 구현 가이드라인
106
  ### 5.1 코드 구조
107
- - 모듈화된 코드 구조 사용 (게임 상태, 렌더링, 입력 처리 분리)
108
- - 객체지향 또는 함수형 프로그래밍 패턴 일관되게 적용
109
- - 게임 루프 최적화 (requestAnimationFrame 활용)
110
- - 적절한 주석과 명확한 변수/함수명 사용
111
 
112
  ### 5.2 성능 최적화
113
- - 스프라이트 시트 및 에셋 사전 로딩
114
- - DOM 조작 최소화 및 캔버스 기반 렌더링 고려
115
- - 메모리 누수 방지 및 이벤트 리스너 최적화
116
- - 저사양 기기에서도 원활한 실행을 위한 최적화
117
 
118
  ### 5.3 반응형 디자인
119
- - 다양한 화면 크기에 적응하는 레이아웃 구현
120
- - 터치 마우스 입력 모두 지원
121
- - 디바이스 방향 변경에 대응하는 UI/UX 구현
122
- - 모바일 환경에서의 게임플레이 최적화
123
-
124
- ## 6. 사용 가능한 외부 라이브러리 (CDN)
125
- - **게임 엔진**: Phaser.js, Three.js, PixiJS
126
- - **애니메이션**: Anime.js, GSAP
127
- - **유틸리티**: jQuery, Lodash
128
- - **사운드**: Howler.js, Tone.js
129
- - **물리 엔진**: Matter.js, Box2D.js
130
 
131
  ## 7. 접근성 및 포용성
132
- - 색맹/색약 사용자를 위한 대체 색상 체계 고려
133
- - 키보드 탐색 및 스크린 리더 호환성 구현
134
- - 난이도 조절 옵션 제공으로 다양한 숙련도의 플레이어 포용
135
- - 콘트라스트 비율 및 글꼴 크기 조절 옵션 제공
136
 
137
  ## 8. 제약사항 및 유의사항
138
- - 외부 API 호출 금지 (모든 기능은 클라이언트 측에서 구현)
139
- - 민감한 데이터 사용 금지
140
- - 필요시 모의(mock) 데이터 또는 로컬 데이터 활용
141
- - 빌드 단계 없이 브라우저에서 직접 실행 가능해야 함
142
 
143
  ## 9. 출력 형식
144
  - HTML 코드 블록으로만 코드 반환
145
  - 추가 설명 없이 즉시 실행 가능한 코드만 제공
146
- - 모델명과 지시문 노출 금지
147
 
148
  ## 10. 코드 품질 기준
149
- - 효율적이고 간결한 코드 작성
150
- - 핵심 게임플레이 메커니즘에 우선 집중
151
- - 깨끗하고 유지보수 가능한 코드 구조
152
- - 불필요한 주석이나 과도하게 장황한 구현 방식 지양
153
-
 
 
 
 
 
 
 
154
  """
155
 
156
 
@@ -209,15 +203,19 @@ openai_client = openai.OpenAI(api_key=YOUR_OPENAI_TOKEN)
209
 
210
  async def try_claude_api(system_message, claude_messages, timeout=15):
211
  """
212
- Claude API 호출 (스트리밍)
213
  """
214
  try:
 
 
 
215
  start_time = time.time()
216
  with claude_client.messages.stream(
217
  model="claude-3-7-sonnet-20250219",
218
  max_tokens=19800,
219
- system=system_message,
220
- messages=claude_messages
 
221
  ) as stream:
222
  collected_content = ""
223
  for chunk in stream:
@@ -234,15 +232,19 @@ async def try_claude_api(system_message, claude_messages, timeout=15):
234
 
235
  async def try_openai_api(openai_messages):
236
  """
237
- OpenAI API 호출 (스트리밍)
238
  """
239
  try:
 
 
 
 
240
  stream = openai_client.chat.completions.create(
241
  model="o3",
242
  messages=openai_messages,
243
  stream=True,
244
  max_tokens=19800,
245
- temperature=0.3
246
  )
247
  collected_content = ""
248
  for chunk in stream:
@@ -251,200 +253,29 @@ async def try_openai_api(openai_messages):
251
  yield collected_content
252
  except Exception as e:
253
  raise e
254
-
255
 
256
  # ------------------------
257
  # 4) 템플릿(하나로 통합)
258
  # ------------------------
259
 
260
  def load_json_data():
261
- """
262
- 모든 템플릿(원래 best/trending/new를 통합)
263
- """
264
  data_list = [
265
  {
266
  "name": "[게임] 테트리스 클론",
267
- "image_url": "data:image/png;base64," + get_image_base64('tetris.png'),
268
- "prompt": "Create a Tetris-like puzzle game with arrow key controls, line-clearing mechanics, and increasing difficulty levels."
269
  },
270
  {
271
  "name": "[게임] 체스",
272
- "image_url": "data:image/png;base64," + get_image_base64('chess.png'),
273
- "prompt": "Build an interactive Chess game with a basic AI opponent and drag-and-drop piece movement. Keep track of moves and detect check/checkmate."
274
- },
275
- {
276
- "name": "[게임] 카드 매칭 게임",
277
- "image_url": "data:image/png;base64," + get_image_base64('memory.png'),
278
- "prompt": "Design a memory matching card game with flip animations, scoring system, and multiple difficulty levels."
279
- },
280
- {
281
- "name": "[게임] 슈팅 게임 (Space Shooter)",
282
- "image_url": "data:image/png;base64," + get_image_base64('spaceshooter.png'),
283
- "prompt": "Create a space shooter game with enemy waves, collision detection, and power-ups. Use keyboard or mouse controls for ship movement."
284
- },
285
- {
286
- "name": "[게임] 슬라이드 퍼즐",
287
- "image_url": "data:image/png;base64," + get_image_base64('slidepuzzle.png'),
288
- "prompt": "Implement a slide puzzle game using images or numbers. Include shuffle functionality, move counter, and difficulty settings."
289
- },
290
- {
291
- "name": "[게임] 뱀 게임 (Snake)",
292
- "image_url": "data:image/png;base64," + get_image_base64('snake.png'),
293
- "prompt": "Implement the classic Snake game with grid-based movement, score tracking, and increasing speed. Use arrow keys for control."
294
- },
295
- {
296
- "name": "[게임] 브레이크아웃 (벽돌깨기)",
297
- "image_url": "data:image/png;base64," + get_image_base64('breakout.png'),
298
- "prompt": "Build a classic breakout game with paddle, ball, and bricks. Increase ball speed and track lives/score."
299
- },
300
- {
301
- "name": "[게임] 타워 디펜스",
302
- "image_url": "data:image/png;base64," + get_image_base64('towerdefense.png'),
303
- "prompt": "Create a tower defense game with multiple tower types and enemy waves. Include an upgrade system and resource management."
304
- },
305
- {
306
- "name": "[게임] 런닝 점프 (Endless Runner)",
307
- "image_url": "data:image/png;base64," + get_image_base64('runner.png'),
308
- "prompt": "Design an endless runner with side-scrolling obstacles. Use keyboard or mouse to jump and avoid collisions."
309
- },
310
- {
311
- "name": "[게임] 플랫포머 (Platformer)",
312
- "image_url": "data:image/png;base64," + get_image_base64('platformer.png'),
313
- "prompt": "Implement a platformer game with character movement, jumping, and collectible items. Use arrow keys for control."
314
- },
315
- {
316
- "name": "[게임] 미로 찾기 (Maze)",
317
- "image_url": "data:image/png;base64," + get_image_base64('maze.png'),
318
- "prompt": "Generate a random maze and allow the player to navigate from start to finish. Include a timer and pathfinding animations."
319
- },
320
- {
321
- "name": "[게임] 미션 RPG",
322
- "image_url": "data:image/png;base64," + get_image_base64('rpg.png'),
323
- "prompt": "Build a simple top-down RPG with tile-based movement, monsters, and loot. Use arrow keys for movement and track player stats."
324
- },
325
- {
326
- "name": "[게임] Match-3 퍼즐",
327
- "image_url": "data:image/png;base64," + get_image_base64('match3.png'),
328
- "prompt": "Create a match-3 puzzle game with swipe-based mechanics, special tiles, and combo scoring."
329
- },
330
- {
331
- "name": "[게임] 하늘 나는 새 (Flappy Bird)",
332
- "image_url": "data:image/png;base64," + get_image_base64('flappy.png'),
333
- "prompt": "Implement a Flappy Bird clone with space bar or mouse click to flap, randomized pipe positions, and score tracking."
334
- },
335
- {
336
- "name": "[게임] 그림 찾기 (Spot the Difference)",
337
- "image_url": "data:image/png;base64," + get_image_base64('spotdiff.png'),
338
- "prompt": "Build a spot-the-difference game using pairs of similar images. Track remaining differences and time limit."
339
- },
340
- {
341
- "name": "[게임] 타이핑 게임",
342
- "image_url": "data:image/png;base64," + get_image_base64('typing.png'),
343
- "prompt": "Create a typing speed test game where words fall from the top. Type them before they reach the bottom to score points."
344
- },
345
- {
346
- "name": "[게임] 미니 골프",
347
- "image_url": "data:image/png;base64," + get_image_base64('minigolf.png'),
348
- "prompt": "Implement a mini golf game with physics-based ball movement. Include multiple holes and scoring based on strokes."
349
- },
350
- {
351
- "name": "[게임] 낚시 게임",
352
- "image_url": "data:image/png;base64," + get_image_base64('fishing.png'),
353
- "prompt": "Design a fishing game where the player casts a line, reels fish, and can upgrade gear. Manage fish spawn rates and scoring."
354
- },
355
- {
356
- "name": "[게임] 빙고",
357
- "image_url": "data:image/png;base64," + get_image_base64('bingo.png'),
358
- "prompt": "Build a bingo game with randomly generated boards and a calling system. Automatically check winning lines."
359
- },
360
- {
361
- "name": "[게임] 리듬 게임",
362
- "image_url": "data:image/png;base64," + get_image_base64('rhythm.png'),
363
- "prompt": "Create a web-based rhythm game using keyboard inputs. Time hits accurately for score, and add background music."
364
- },
365
- {
366
- "name": "[게임] 2D 레이싱",
367
- "image_url": "data:image/png;base64," + get_image_base64('racing2d.png'),
368
- "prompt": "Implement a top-down 2D racing game with track boundaries, lap times, and multiple AI opponents."
369
- },
370
- {
371
- "name": "[게임] 퀴즈 게임",
372
- "image_url": "data:image/png;base64," + get_image_base64('quiz.png'),
373
- "prompt": "Build a quiz game with multiple-choice questions, scoring, and a timer. Randomize question order each round."
374
- },
375
- {
376
- "name": "[게임] 돌 맞추기 (Shooting Gallery)",
377
- "image_url": "data:image/png;base64," + get_image_base64('gallery.png'),
378
- "prompt": "Create a shooting gallery game with moving targets, limited ammo, and a time limit. Track hits and misses."
379
- },
380
- {
381
- "name": "[게임] 주사위 보드",
382
- "image_url": "data:image/png;base64," + get_image_base64('diceboard.png'),
383
- "prompt": "Implement a dice-based board game with multiple squares, events, and item usage. Players take turns rolling."
384
- },
385
- {
386
- "name": "[게임] 좀비 서바이벌",
387
- "image_url": "data:image/png;base64," + get_image_base64('zombie.png'),
388
- "prompt": "Design a top-down zombie survival game with wave-based enemies, pickups, and limited ammo. Track score and health."
389
- },
390
- {
391
- "name": "[게임] 축구 게임 (Penalty Kick)",
392
- "image_url": "data:image/png;base64," + get_image_base64('soccer.png'),
393
- "prompt": "Build a simple penalty shootout game with aiming, power bars, and a goalie AI that guesses shots randomly."
394
- },
395
- {
396
- "name": "[게임] Minesweeper",
397
- "image_url": "data:image/png;base64," + get_image_base64('minesweeper.png'),
398
- "prompt": "Implement the classic Minesweeper game with left-click reveal, right-click flags, and adjacency logic for numbers."
399
- },
400
- {
401
- "name": "[게임] Connect Four",
402
- "image_url": "data:image/png;base64," + get_image_base64('connect4.png'),
403
- "prompt": "Create a Connect Four game with drag-and-drop or click-based input, alternating turns, and a win check algorithm."
404
- },
405
- {
406
- "name": "[게임] 스크래블 (단어 퍼즐)",
407
- "image_url": "data:image/png;base64," + get_image_base64('scrabble.png'),
408
- "prompt": "Build a Scrabble-like word puzzle game with letter tiles, scoring, and a local dictionary for validation."
409
- },
410
- {
411
- "name": "[게임] 2D 슈팅 (Tank Battle)",
412
- "image_url": "data:image/png;base64," + get_image_base64('tank.png'),
413
- "prompt": "Implement a 2D tank battle game with destructible terrain, power-ups, and AI or multiplayer functionality."
414
- },
415
- {
416
- "name": "[게임] 젬 크러쉬",
417
- "image_url": "data:image/png;base64," + get_image_base64('gemcrush.png'),
418
- "prompt": "Create a gem-crushing puzzle game where matching gems cause chain reactions. Track combos and score bonuses."
419
- },
420
- {
421
- "name": "[게임] Shooting Tower",
422
- "image_url": "data:image/png;base64," + get_image_base64('tower.png'),
423
- "prompt": "Design a 2D defense game where a single tower shoots incoming enemies in waves. Upgrade the tower's stats over time."
424
- },
425
- {
426
- "name": "[게임] 좀비 러너",
427
- "image_url": "data:image/png;base64," + get_image_base64('zombierunner.png'),
428
- "prompt": "Make a side-scrolling runner where a character avoids zombies and obstacles, collecting power-ups along the way."
429
- },
430
- {
431
- "name": "[게임] 스킬 액션 RPG",
432
- "image_url": "data:image/png;base64," + get_image_base64('actionrpg.png'),
433
- "prompt": "Create a small action RPG with WASD movement, an attack button, special moves, leveling, and item drops."
434
  },
 
435
  ]
436
  return data_list
437
 
438
- def load_all_templates():
439
- """
440
- 모든 템플릿을 하나로 보여주는 함수
441
- """
442
- return create_template_html("🎮 모든 게임 템플릿", load_json_data())
443
-
444
-
445
  def create_template_html(title, items):
446
  """
447
- 폰트를 작게 조정
448
  """
449
  html_content = r"""
450
  <style>
@@ -467,13 +298,6 @@ def create_template_html(title, items):
467
  transform: translateY(-4px);
468
  box-shadow: 0 6px 12px rgba(0,0,0,0.1);
469
  }
470
- .card-image {
471
- width: 100%;
472
- height: 140px;
473
- object-fit: cover;
474
- border-radius: 8px;
475
- margin-bottom: 10px;
476
- }
477
  .card-name {
478
  font-weight: bold;
479
  margin-bottom: 8px;
@@ -496,12 +320,12 @@ def create_template_html(title, items):
496
  </style>
497
  <div class="prompt-grid">
498
  """
 
499
  for item in items:
500
  card_html = f"""
501
- <div class="prompt-card" onclick="copyToInput(this)" data-prompt="{html.escape(item.get('prompt', ''))}">
502
- <img src="{item.get('image_url', '')}" class="card-image" loading="lazy" alt="{html.escape(item.get('name', ''))}">
503
- <div class="card-name">{html.escape(item.get('name', ''))}</div>
504
- <div class="card-prompt">{html.escape(item.get('prompt', ''))}</div>
505
  </div>
506
  """
507
  html_content += card_html
@@ -522,117 +346,85 @@ function copyToInput(card) {
522
  """
523
  return gr.HTML(value=html_content)
524
 
 
 
 
 
 
 
525
 
526
  # ------------------------
527
  # 5) 배포/부스트/기타 유틸
528
  # ------------------------
529
 
530
- def generate_space_name():
531
- letters = string.ascii_lowercase
532
- return ''.join(random.choice(letters) for i in range(6))
533
-
534
- def deploy_to_vercel(code: str):
535
- """
536
- Vercel에 배포하는 함수 (예시)
537
- """
538
- try:
539
- token = "A8IFZmgW2cqA4yUNlLPnci0N" # 실제 토큰 필요
540
- if not token:
541
- return "Vercel 토큰이 설정되지 않았습니다."
542
-
543
- project_name = ''.join(random.choice(string.ascii_lowercase) for i in range(6))
544
- deploy_url = "https://api.vercel.com/v13/deployments"
545
- headers = {
546
- "Authorization": f"Bearer {token}",
547
- "Content-Type": "application/json"
548
- }
549
- package_json = {
550
- "name": project_name,
551
- "version": "1.0.0",
552
- "private": True,
553
- "dependencies": {
554
- "vite": "^5.0.0"
555
- },
556
- "scripts": {
557
- "dev": "vite",
558
- "build": "echo 'No build needed' && mkdir -p dist && cp index.html dist/",
559
- "preview": "vite preview"
560
- }
561
- }
562
- files = [
563
- {
564
- "file": "index.html",
565
- "data": code
566
- },
567
- {
568
- "file": "package.json",
569
- "data": json.dumps(package_json, indent=2)
570
- }
571
- ]
572
- project_settings = {
573
- "buildCommand": "npm run build",
574
- "outputDirectory": "dist",
575
- "installCommand": "npm install",
576
- "framework": None
577
- }
578
- deploy_data = {
579
- "name": project_name,
580
- "files": files,
581
- "target": "production",
582
- "projectSettings": project_settings
583
- }
584
- deploy_response = requests.post(deploy_url, headers=headers, json=deploy_data)
585
- if deploy_response.status_code != 200:
586
- return f"배포 실패: {deploy_response.text}"
587
- deployment_url = f"{project_name}.vercel.app"
588
- time.sleep(5)
589
- return f"""배포 완료! <a href="https://{deployment_url}" target="_blank" style="color: #1890ff; text-decoration: underline; cursor: pointer;">https://{deployment_url}</a>"""
590
- except Exception as e:
591
- return f"배포 중 오류 발생: {str(e)}"
592
-
593
  def remove_code_block(text):
594
  """
595
  More robust function to extract code from markdown code blocks
596
  텍스트에서 ```html 및 ``` 태그를 완전히 제거하는 함수
597
  """
598
- # ```html 태그로 둘러싸인 코드 블록 찾기
599
  pattern = r'```html\s*([\s\S]+?)\s*```'
600
  match = re.search(pattern, text, re.DOTALL)
601
  if match:
602
  return match.group(1).strip()
603
 
604
- # 일반 코드 블록 처리
605
  pattern = r'```(?:\w+)?\s*([\s\S]+?)\s*```'
606
  match = re.search(pattern, text, re.DOTALL)
607
  if match:
608
  return match.group(1).strip()
609
 
610
- # 텍스트에 ```html과 ```가 포함된 경우 명시적으로 제거
611
  text = re.sub(r'```html\s*', '', text)
612
  text = re.sub(r'\s*```', '', text)
613
-
614
- # 코드 블록이 없는 경우 원본 텍스트 반환
615
  return text.strip()
616
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
617
  def send_to_sandbox(code):
618
  """
619
- Improved function to create iframe with proper code cleaning
620
- ```html 태그가 확실히 제거된 코드를 iframe으로 렌더링
621
  """
622
- # 코드에서 마크다운 표기 제거
623
  clean_code = remove_code_block(code)
 
624
 
625
- # 디버깅: 코드 앞부분 확인
626
- code_start = clean_code[:50] if len(clean_code) > 50 else clean_code
627
- print(f"Code start: {code_start}")
628
-
629
- # ```html 태그가 여전히 있으면 명시적으로 제거
630
  if clean_code.startswith('```html'):
631
  clean_code = clean_code[7:].strip()
632
  if clean_code.endswith('```'):
633
  clean_code = clean_code[:-3].strip()
634
 
635
- # 기본 HTML 구조 추가
636
  if not clean_code.strip().startswith('<!DOCTYPE') and not clean_code.strip().startswith('<html'):
637
  clean_code = f"""<!DOCTYPE html>
638
  <html>
@@ -645,60 +437,59 @@ def send_to_sandbox(code):
645
  {clean_code}
646
  </body>
647
  </html>"""
648
-
649
- # iframe 생성
650
  encoded_html = base64.b64encode(clean_code.encode('utf-8')).decode('utf-8')
651
  data_uri = f"data:text/html;charset=utf-8;base64,{encoded_html}"
652
  return f'<iframe src="{data_uri}" width="100%" height="920px" style="border:none;"></iframe>'
653
 
654
  def boost_prompt(prompt: str) -> str:
655
- """
656
- '증���강' 버튼 눌렀을 때 프롬프트를 좀 더 풍부하게 생성 (예시)
657
- """
658
  if not prompt:
659
  return ""
660
  boost_system_prompt = """당신은 웹 게임 개발 프롬프트 전문가입니다.
661
- 주어진 프롬프트를 분석하여 더 상세하고 전문적인 요구사항으로 확장하되,
662
  원래 의도와 목적은 그대로 유지하면서 다음 관점들을 고려하여 증강하십시오:
663
 
664
- 1. 게임 플레이 재미와 난이도 밸런스
665
- 2. 인터랙티브 그래픽 애니메이션
666
- 3. 사용자 경험 최적화 (UI/UX)
667
- 4. 성능 최적화
668
- 5. 접근성과 호환성
669
-
670
- 기존 SystemPrompt의 모든 규칙을 준수하면서 증강된 프롬프트를 생성하십시오.
 
 
 
 
671
  """
672
  try:
673
- # Claude API 시도
674
  try:
675
  response = claude_client.messages.create(
676
  model="claude-3-7-sonnet-20250219",
677
- max_tokens=2000,
 
678
  messages=[{
679
  "role": "user",
680
- "content": f"다음 게임 프롬프트를 분석하고 증강하시오: {prompt}"
681
- }]
 
682
  )
683
  if hasattr(response, 'content') and len(response.content) > 0:
684
  return response.content[0].text
685
  raise Exception("Claude API 응답 형식 오류")
686
  except Exception:
687
- # OpenAI API로 fallback
688
  completion = openai_client.chat.completions.create(
689
  model="gpt-4",
690
  messages=[
691
  {"role": "system", "content": boost_system_prompt},
692
- {"role": "user", "content": f"다음 게임 프롬프트를 분석하고 증강하시오: {prompt}"}
693
  ],
694
- max_tokens=2000,
695
- temperature=0.7
696
  )
697
  if completion.choices and len(completion.choices) > 0:
698
  return completion.choices[0].message.content
699
  raise Exception("OpenAI API 응답 형식 오류")
700
  except Exception:
701
- # 실패 시 원본 그대로 반환
702
  return prompt
703
 
704
  def handle_boost(prompt: str):
@@ -709,29 +500,17 @@ def handle_boost(prompt: str):
709
  return prompt, gr.update(active_key="empty")
710
 
711
  def history_render(history: History):
712
- """
713
- 히스토리 Drawer 열고, Chatbot UI에 히스토리 반영
714
- """
715
  return gr.update(open=True), history
716
 
717
  def execute_code(query: str):
718
- """
719
- Improved function to execute code directly from input
720
- 코드 실행 시 ```html 태그를 확실히 제거
721
- """
722
  if not query or query.strip() == '':
723
  return None, gr.update(active_key="empty")
724
  try:
725
- # 코드 정제 - 마크다운 태그 철저히 제거
726
  clean_code = remove_code_block(query)
727
-
728
- # ```html 태그가 여전히 있으면 명시적으로 제거
729
  if clean_code.startswith('```html'):
730
  clean_code = clean_code[7:].strip()
731
  if clean_code.endswith('```'):
732
  clean_code = clean_code[:-3].strip()
733
-
734
- # HTML 구조 추가
735
  if not clean_code.strip().startswith('<!DOCTYPE') and not clean_code.strip().startswith('<html'):
736
  if not ('<body' in clean_code and '</body>' in clean_code):
737
  clean_code = f"""<!DOCTYPE html>
@@ -745,7 +524,6 @@ def execute_code(query: str):
745
  {clean_code}
746
  </body>
747
  </html>"""
748
-
749
  return send_to_sandbox(clean_code), gr.update(active_key="render")
750
  except Exception as e:
751
  print(f"Execute code error: {str(e)}")
@@ -759,7 +537,7 @@ def execute_code(query: str):
759
  class Demo:
760
  def __init__(self):
761
  pass
762
-
763
  async def generation_code(self, query: Optional[str], _setting: Dict[str, str], _history: Optional[History]):
764
  if not query or query.strip() == '':
765
  query = random.choice(DEMO_LIST)['description']
@@ -767,11 +545,22 @@ class Demo:
767
  if _history is None:
768
  _history = []
769
 
 
 
 
 
 
 
 
 
 
 
 
770
  messages = history_to_messages(_history, _setting['system'])
771
  system_message = messages[0]['content']
772
 
773
  claude_messages = [
774
- {"role": msg["role"] if msg["role"] != "system" else "user", "content": msg["content"]}
775
  for msg in messages[1:] + [{'role': Role.USER, 'content': query}]
776
  if msg["content"].strip() != ''
777
  ]
@@ -785,7 +574,6 @@ class Demo:
785
  openai_messages.append({"role": "user", "content": query})
786
 
787
  try:
788
- # "Generating code..." 출력
789
  yield [
790
  "Generating code...",
791
  _history,
@@ -794,10 +582,8 @@ class Demo:
794
  gr.update(open=True)
795
  ]
796
  await asyncio.sleep(0)
797
-
798
  collected_content = None
799
  try:
800
- # Claude API 시도
801
  async for content in try_claude_api(system_message, claude_messages):
802
  yield [
803
  content,
@@ -809,7 +595,6 @@ class Demo:
809
  await asyncio.sleep(0)
810
  collected_content = content
811
  except Exception:
812
- # OpenAI fallback
813
  async for content in try_openai_api(openai_messages):
814
  yield [
815
  content,
@@ -822,36 +607,45 @@ class Demo:
822
  collected_content = content
823
 
824
  if collected_content:
825
- # 히스토리 갱신
826
- _history = messages_to_history([
827
- {'role': Role.SYSTEM, 'content': system_message}
828
- ] + claude_messages + [{
829
- 'role': Role.ASSISTANT,
830
- 'content': collected_content
831
- }])
832
-
833
- # 최종 결과(코드) + 샌드박스 미리보기
834
- # 코드 블록 추출 시 확실하게 ```html 제거
835
  clean_code = remove_code_block(collected_content)
836
-
837
- # 디버그 출력
838
- print(f"Original content start: {collected_content[:30]}")
839
- print(f"Cleaned code start: {clean_code[:30]}")
840
-
841
- # 마크다운 형식의 collected_content는 그대로 출력
842
- # 하지만 샌드박스에는 정제된 clean_code 전달
843
- yield [
844
- collected_content,
845
- _history,
846
- send_to_sandbox(clean_code),
847
- gr.update(active_key="render"),
848
- gr.update(open=True)
849
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
850
  else:
851
  raise ValueError("No content was generated from either API")
852
  except Exception as e:
853
  raise ValueError(f'Error calling APIs: {str(e)}')
854
-
855
  def clear_history(self):
856
  return []
857
 
@@ -871,7 +665,28 @@ theme = gr.themes.Soft(
871
  )
872
 
873
  with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
874
- gr.HTML("""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
875
  <style>
876
  /* 전체 앱 스타일 */
877
  :root {
@@ -896,7 +711,7 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
896
  .app-header {
897
  text-align: center;
898
  padding: 1.5rem 1rem;
899
- margin-bottom: 1.5rem;
900
  background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
901
  border-radius: var(--radius);
902
  box-shadow: var(--shadow);
@@ -917,122 +732,171 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
917
  margin: 0 auto;
918
  }
919
 
920
- /* 패널 스타일 */
921
- .panel {
922
- background-color: var(--panel-color);
923
  border-radius: var(--radius);
 
924
  box-shadow: var(--shadow);
 
 
925
  overflow: hidden;
926
- transition: transform 0.3s ease;
927
  }
928
 
929
- .panel:hover {
930
- transform: translateY(-3px);
931
  }
932
 
933
- /* 버튼 스타일 */
934
- .ant-btn {
935
- border-radius: 8px;
936
- font-weight: 500;
937
- transition: all 0.3s ease;
938
  }
939
 
940
- .ant-btn:hover {
941
- transform: translateY(-2px);
942
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
943
  }
944
 
945
- .ant-btn-primary {
946
- background: var(--primary-color);
947
- border-color: var(--primary-color);
 
948
  }
949
 
950
- .ant-btn-primary:hover {
951
- background: var(--secondary-color);
952
- border-color: var(--secondary-color);
953
  }
954
 
955
- /* 인풋 스타일 */
956
- .ant-input, .ant-input-textarea textarea {
957
- border-radius: 8px;
958
- border: 1px solid #e6e6e6;
959
- transition: all 0.3s ease;
960
  }
961
 
962
- .ant-input:focus, .ant-input-textarea textarea:focus {
963
- border-color: var(--accent-color);
964
- box-shadow: 0 0 0 2px rgba(184, 190, 221, 0.2);
 
965
  }
966
 
967
- /* 랜더 헤더 스타일 */
968
- .render_header {
969
- background: linear-gradient(to right, var(--primary-color), var(--accent-color));
970
- border-top-left-radius: var(--radius);
971
- border-top-right-radius: var(--radius);
972
  }
973
 
974
- .header_btn {
975
- background-color: rgba(255, 255, 255, 0.7);
 
 
 
 
 
 
976
  }
977
 
978
- /* 컨텐츠 영역 */
979
- .right_content, .html_content {
980
- border-radius: 0 0 var(--radius) var(--radius);
 
 
 
981
  }
982
 
983
- /* 도움말 스타일 */
984
- .help-text {
985
- color: #666;
986
- font-size: 0.9rem;
987
- margin-top: 0.5rem;
 
 
 
 
988
  }
989
-
990
- /* 텍스트 영역 높이 조정 */
991
- .ant-input-textarea-large textarea {
992
- min-height: 200px !important;
993
  }
994
  </style>
995
 
996
- <div class="app-header">
997
- <h1>🎮 Vibe Game Craft</h1>
998
- <p>설명을 입력하면 웹 기반 HTML5, JavaScript, CSS 게임을 생성합니다. 직관적인 인터페이스로 쉽게 게임을 만들고, 실시간으로 미리보기를 확인하세요.</p>
999
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1000
  """)
1001
 
1002
  history = gr.State([])
1003
  setting = gr.State({"system": SystemPrompt})
 
 
 
 
 
 
 
 
1004
 
1005
  with ms.Application() as app:
1006
  with antd.ConfigProvider():
1007
 
1008
  # code Drawer
1009
- with antd.Drawer(open=False, title="code", placement="left", width="750px") as code_drawer:
1010
  code_output = legacy.Markdown()
1011
 
1012
  # history Drawer
1013
- with antd.Drawer(open=False, title="history", placement="left", width="900px") as history_drawer:
1014
  history_output = legacy.Chatbot(
1015
  show_label=False, flushing=False, height=960, elem_classes="history_chatbot"
1016
  )
1017
 
1018
- # templates Drawer (하나로 통합)
1019
  with antd.Drawer(
1020
  open=False,
1021
- title="Templates",
1022
  placement="right",
1023
  width="900px",
1024
  elem_classes="session-drawer"
1025
  ) as session_drawer:
1026
  with antd.Flex(vertical=True, gap="middle"):
1027
- gr.Markdown("### Available Game Templates (All-in-One)")
1028
  session_history = gr.HTML(elem_classes="session-history")
1029
- close_btn = antd.Button("Close", type="default", elem_classes="close-btn")
1030
 
1031
- # 좌우 레이아웃 + 상단 정렬
1032
- with antd.Row(gutter=[32, 12], align="top") as layout:
1033
 
1034
- # 왼쪽 Col: 미리보기 (상단 정렬)
1035
- with antd.Col(span=24, md=16):
1036
  with ms.Div(elem_classes="right_panel panel"):
1037
  gr.HTML(r"""
1038
  <div class="render_header">
@@ -1051,48 +915,48 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
1051
  with antd.Tabs.Item(key="render"):
1052
  sandbox = gr.HTML(elem_classes="html_content")
1053
 
1054
- # 오른쪽 Col: 메뉴 버튼들 + 입력창 + 배포 결과
1055
- with antd.Col(span=24, md=8):
1056
- # 헤더 추가하여 왼쪽 패널과 높이 맞추기
1057
- gr.HTML(r"""
1058
- <div class="render_header" style="visibility: hidden;">
1059
- <span class="header_btn"></span>
1060
- <span class="header_btn"></span>
1061
- <span class="header_btn"></span>
1062
- </div>
1063
- """)
1064
- # ── (1) 상단 메뉴 Bar (코드보기, 히스토리, 템플릿=단 하나) ──
1065
- with antd.Flex(gap="small", elem_classes="setting-buttons", justify="end"):
1066
- codeBtn = antd.Button("🧑‍💻코드 보기", type="default")
1067
- historyBtn = antd.Button("📜히스토리", type="default")
1068
- template_btn = antd.Button("🎮템플릿", type="default")
1069
-
1070
- # ── (2) 입력창 ──
1071
- with antd.Flex(vertical=True, gap="middle", wrap=True, elem_classes="panel input-panel"):
1072
- # rows 파라미터를 사용하지 않고 CSS로 높이 조정
1073
  input_text = antd.InputTextarea(
1074
  size="large",
1075
  allow_clear=True,
1076
  placeholder=random.choice(DEMO_LIST)['description'],
1077
- max_length=100000 # 입력 최대 길이 증가
1078
  )
1079
  gr.HTML('<div class="help-text">💡 원하는 게임의 설명을 입력하세요. 예: "테트리스 게임 제작해줘."</div>')
1080
 
1081
- # ── (3) 액션 버튼들 (Send, Boost, Code실행, 배포, 클리어) ──
1082
- with antd.Flex(gap="small", justify="space-between"):
1083
- btn = antd.Button("전송", type="primary", size="large")
1084
- boost_btn = antd.Button("증강", type="default", size="large")
1085
- execute_btn = antd.Button("코드", type="default", size="large")
1086
- deploy_btn = antd.Button("배포", type="default", size="large")
1087
- clear_btn = antd.Button("클리어", type="default", size="large")
1088
-
1089
- # ── (4) 배포 결과 영역 ──
1090
- deploy_result = gr.HTML(label="배포 결과")
1091
-
1092
-
1093
- # ---- 이벤트 / 콜백 ----
1094
-
1095
- # (A) Code Drawer 열기/닫기
 
1096
  codeBtn.click(
1097
  lambda: gr.update(open=True),
1098
  inputs=[],
@@ -1104,7 +968,7 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
1104
  outputs=[code_drawer]
1105
  )
1106
 
1107
- # (B) 히스토리 Drawer 열기/닫기
1108
  historyBtn.click(
1109
  history_render,
1110
  inputs=[history],
@@ -1116,7 +980,7 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
1116
  outputs=[history_drawer]
1117
  )
1118
 
1119
- # (C) 템플릿 Drawer 열기/닫기 (하나로 통합)
1120
  template_btn.click(
1121
  fn=lambda: (gr.update(open=True), load_all_templates()),
1122
  outputs=[session_drawer, session_history],
@@ -1131,39 +995,195 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
1131
  outputs=[session_drawer, session_history]
1132
  )
1133
 
1134
- # (D) 'Send' 버튼 => 코드 생성
1135
  btn.click(
1136
  demo_instance.generation_code,
1137
  inputs=[input_text, setting, history],
1138
  outputs=[code_output, history, sandbox, state_tab, code_drawer]
1139
  )
1140
 
1141
- # (E) '클리어' 버튼 => 히스토리 초기화
1142
  clear_btn.click(
1143
  demo_instance.clear_history,
1144
  inputs=[],
1145
  outputs=[history]
1146
  )
1147
 
1148
- # (F) 'Boost' 버튼 => 프롬프트 보강
1149
  boost_btn.click(
1150
  fn=handle_boost,
1151
  inputs=[input_text],
1152
  outputs=[input_text, state_tab]
1153
  )
1154
 
1155
- # (G) 'Code실행' 버튼 => 미리보기 iframe 로드
1156
  execute_btn.click(
1157
  fn=execute_code,
1158
  inputs=[input_text],
1159
  outputs=[sandbox, state_tab]
1160
  )
1161
 
1162
- # (H) '배포' 버튼 => Vercel
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1163
  deploy_btn.click(
1164
- fn=lambda code: deploy_to_vercel(remove_code_block(code)) if code else "코드가 없습니다.",
1165
- inputs=[code_output],
1166
- outputs=[deploy_result]
1167
  )
1168
 
1169
 
@@ -1177,4 +1197,4 @@ if __name__ == "__main__":
1177
  demo.queue(default_concurrency_limit=20).launch(ssr_mode=False)
1178
  except Exception as e:
1179
  print(f"Initialization error: {e}")
1180
- raise
 
26
  # ------------------------
27
 
28
  DEMO_LIST = [
29
+ {"description": "블록이 위에서 떨어지는 클래식 테트리스 게임을 개발해주세요. 화살표 키로 조작하며, 가로줄이 채워지면 해당 줄이 제거되고 점수가 올라가는 메커니즘이 필요합니다. 난이도는 시간이 지날수록 블록이 ���라지도록 구현하고, 게임오버 조건과 점수 표시 기능을 포함해주세요."},
30
+ {"description": " 명이 번갈아가며 플레이할 있는 체스 게임을 만들어주세요. 기본적인 체스 규칙(킹, 퀸, 룩, 비숍, 나이트, 폰의 이동 규칙)을 구현하고, 체크와 체크메이트 감지 기능이 필요합니다. 드래그 앤 드롭으로 말을 움직일 수 있게 하며, 이동 기록도 표시해주세요."},
31
+ {"description": "짝을 맞추는 메모리 카드 게임을 개발해주세요. 카드를 뒤집으면 그림이 나타나고, 같은 그림의 카드 장을 찾으면 점수를 얻는 방식입니다. 카드 뒤집기 애니메이션과 함께 시도 횟수를 기록하는 점수 시스템, 그리고 쉬움/보통/어려움 난이도 선택 기능(카드 수 변경)도 구현해주세요."},
32
+ {"description": "플레이어가 우주선을 조종하여 우주선을 파괴하는 슈팅 게임을 만들어주세요. 키보드 방향키로 움직이고 스페이스바로 발사하며, 다양한 웨이브가 공격해오는 구조입니다. 충돌 감지 시스템과 함께 파워업 아이템(방패, 다중 발사, 속도 증가 등)을 구현하고, 난이도가 점진적으로 증가하는 시스템을 추가해주세요."},
33
+ {"description": "3x3 또는 4x4 크기의 슬라이드 퍼즐 게임을 만들어주세요. 숫자나 이미지 조각을 섞은 후, 칸을 이용해 조각들을 올바른 위치로 밀어 맞추는 게임입니다. 섞기 기능과 이동 횟수 카운터, 완성 시 축하 메시지를 표시하고, 난이도 설정(크기 변경)도 구현해주세요."},
34
+ {"description": "고전적인 게임을 구현해주세요. 플레이어는 방향키로 뱀을 조종하여 필드에 랜덤하게 생성되는 먹이를 먹으며, 먹이를 먹을 때마다 뱀의 길이가 늘어납니다. 자신의 몸에 부딪히거나 벽에 부딪히면 게임이 종료되며, 점수는 먹은 먹이의 수에 비례합니다. 시간이 지날수록 뱀의 이동 속도가 빨라지는 난이도 조절 기능도 추가해주세요."},
35
+ {"description": "화면 상단에 여러 줄의 벽돌이 배치된 브레이크아웃 게임을 만들어주세요. 플레이어는 화면 하단의 패들을 좌우로 움직여 공을 튕겨내어 벽돌을 깨야 합니다. 벽돌을 모두 깨면 스테이지 클리어, 공이 바닥에 떨어지면 생명이 감소합니다. 공의 속도는 시간이 지날수록 증가하며, 특수 벽돌(추가 생명, 패들 확장 등)도 구현해주세요."},
36
+ {"description": "길을 따라 이동하는 적들을 방어하는 타워 디펜스 게임을 개발해주세요. 플레이어는 맵의 특정 위치에 다양한 타워(기본 공격, 범위 공격, 감속 효과 등)를 설치하여 적을 물리쳐야 합니다. 웨이브 시스템으로 난이도가 점진적으로 증가하며, 적을 처치하면 자원을 얻어 타워를 업그레이드하거나 새 타워를 건설할 수 있는 경제 시스템을 구현해주세요."},
37
+ {"description": "캐릭터가 끝없이 달리며 장애물을 뛰어넘는 엔드리스 러너 게임을 만들어주세요. 스페이스바나 마우스 클릭으로 점프하여 다가오는 장애물(바위, 구덩이, 적 등)을 피해야 합니다. 거리에 따라 점수가 증가하며, 코인 등의 수집품을 모으는 요소와 파워업(일시적 무적, 자석 효과 등)도 추가해주세요. 시간이 지날수록 게임 속도가 빨라지는 난이도 시스템도 구현해주세요."},
38
+ {"description": "2D 플랫포머 게임을 개발해주세요. 플레이어는 방향키로 캐릭터를 조종하여 발판 위를 이동하고, 스페이스바로 점프하며 코인이나 보석 같은 아이템을 수집합니다. 적 캐릭터(간단한 AI로 움직임)와 함정(가시, 떨어지는 발판 등)을 피해 목표 지점까지 도달하는 레벨 기반 구조로 만들어주세요. 체력 시스템과 체크포인트 기능도 구현해주세요."},
39
+ {"description": "매번 새로운 미로를 자동 생성하는 미로 게임을 만들어주세요. 플레이어는 시작점에서 출발하여 방향키로 캐릭터를 조종해 출구를 찾아야 합니다. 미로 생성 알고리즘(예: 깊이 우선 탐색, 프림 알고리즘 등)을 활용하여 다양한 크기와 복잡도의 미로를 만들고, 타이머로 시간을 측정하며, 선택적으로 최단 경로를 보여주는 힌트 기능도 구현해주세요."},
40
+ {"description": "간단한 턴제 RPG 게임을 개발해주세요. 플레이어는 탑다운 뷰에서 타일 기반으로 이동하며, 몬스터와 마주치면 턴제 전투가 시작됩니다. 기본 공격, 특수 스킬, 아이템 사용 등의 전투 옵션과 함께 레벨업 시스템(경험치, 능력치 상승)을 구현해주세요. 또한 전투에서 승리하면 골드와 아이템을 획득할 수 있으며, 상점에서 장비를 구매하는 기능도 추가해주세요."},
41
+ {"description": "같은 색상/모양의 아이템 3 이상을 일렬로 맞추는 매치-3 퍼즐 게임을 만들어주세요. 아이템을 스와이프하여 위치를 바꾸고, 매치되면 아이템이 사라지며 점수를 얻는 방식입니다. 특수 매치(4개 이상, T자 모양 등)는 특수 아이템을 생성하며, 연속 매치(콤보)는 추가 점수나 보너스 효과를 제공합니다. 목표 점수 또는 제한 시간/이동 횟수 모드를 구현해주세요."},
42
+ {"description": "플래피 버드 스타일의 게임을 개발해주세요. 플레이어는 스페이스바나 마우스 클릭으로 새를 점프시켜 위아래로 움직이는 파이프 사이를 통과해야 합니다. 파이프에 부딪히거나 화면 상단/하단에 닿으면 게임 오버이며, 통과한 파이프 쌍마다 점수가 1점씩 증가합니다. 파이프 간격은 랜덤하게 생성되며, 최고 점수를 로컬 스토리지에 저장하는 기능도 구현해주세요."},
43
+ {"description": " 개의 유사한 이미지에서 차이점을 찾는 게임을 만들어주세요. 5-10개의 차이점이 있는 이미지 쌍을 준비하고, 플레이어가 차이점을 클릭하면 표시되도록 합니다. 제한 시간 내에 모든 차이점을 찾아야 하며, 오답 클릭 시 시간 패널티가 부과됩니다. 힌트 시스템(차이점 하나를 자동으로 표시)과 난이도 선택(쉬움: 차이점이 명확, 어려움: 미묘한 차이)도 구현해주세요."},
44
+ {"description": "화면 상단에서 단어가 떨어지는 타이핑 게임을 개발해주세요. 플레이어는 키보드로 해당 단어를 정확히 입력하여 단어가 바닥에 닿기 전에 제거해야 합니다. 정확히 입력한 단어는 사라지고 점수를 얻으며, 난이도에 따라 단어의 길이와 떨어지는 속도가 조절됩니다. 특수 단어(빨간색 등)는 보너스 점수나 시간 추가 등의 효과를 제공하며, 일정 시간/점수마다 난이도가 상승하는 시스템도 구현해주세요."},
45
+ {"description": "물리 엔진 기반의 미니 골프 게임을 만들어주세요. 플레이어는 마우스 드래그로 공을 치는 방향과 세기를 조절하여 홀에 공을 넣어야 합니다. 다양한 장애물(모래 함정, 물웅덩이, 경사로 등)이 있는 여러 개의 코스를 구현하고, 각 홀마다 타수를 기록하여 최종 점수를 계산합니다. 바람 방향/세기 같은 환경 요소와 함께 궤적 미리보기 기능도 추가해주세요."},
46
+ {"description": "플레이어가 낚시를 즐기는 시뮬레이션 게임을 개발해주세요. 마우스 클릭으로 낚싯줄을 던지고, 물고기가 물면 타이밍 맞추기 미니게임으로 물고기를 낚아야 합니다. 다양한 종류의 물고기(희귀도별 점수 차등)를 구현하고, 낚은 물고기에 따라 골드를 획득하여 더 좋은 낚싯대, 미끼 등을 구매할 수 있는 업그레이드 시스템을 추가해주세요. 시간대나 날씨에 따라 출현하는 물고기가 달라지는 기능도 구현해주세요."},
47
+ {"description": "1인용 또는 AI 대전 빙고 게임을 만들어주세요. 5x5 그리드에 1-25 숫자를 무작위로 배치하고, 번갈아가며 숫자를 선택하여 해당 칸을 마킹합니다. 가로, 세로, 대각선으로 5개의 연속된 마킹이 완성되면 빙고가 되며, 먼저 3빙고를 달성하는 쪽이 승리합니다. 컴퓨터 AI는 랜덤하게 또는 전략적으로(빙고에 가까운 라인 우선) 숫자를 선택하도록 구현하고, 타이머와 승/패 기록 시스템도 추가해주세요."},
48
+ {"description": "화면 하단에서 상단으로 노트가 올라오면 정확한 타이밍에 키를 눌러 점수를 얻는 리듬 게임을 개발해주세요. 4개의 레인(D, F, J, K 키)에 노트가 등장하며, 타이밍 정확도에 따라 Perfect, Good, Miss 등급이 표시됩니다. 배경 음악에 맞춰 노트가 생성되며, 연속 성공 시 콤보 시스템으로 추가 점수를 제공합니다. 난이도 선택(노트 속도와 밀도 조절)과 함께 최종 결과 화면(정확도, 콤보, 등급)도 구현해주세요."},
49
+ {"description": "탑다운 뷰의 2D 레이싱 게임을 만들어주세요. 플레이어는 방향키로 자동차를 조종하여 트랙을 따라 주행하며, 트랙 이탈 시 감속되는 메커니즘을 구현합니다. 여러 AI 경쟁자들과 경쟁하며 3바퀴를 가장 빨리 완주하는 게임 모드와 함께, 시간 제한 내에 체크포인트를 통과하는 타임 어택 모드도 구현해주세요. 다양한 차량 선택지(속도와 핸들링 특성 차등)와 부스트 아이템, 장애물 등도 추가해주세요."},
50
+ {"description": "다양한 카테고리의 퀴즈를 풀어나가는 게임을 개발해주세요. 주어진 질문에 4개의 보기 중 정답을 선택하는 방식으로, 정답 시 점수를 획득하고 오답 시 생명이 감소합니다. 30초 제한 시간 내에 답을 선택해야 하며, 난이도에 따라 질문의 복잡도와 제한 시간이 조절됩니다. 50:50 힌트(오답 2개 제거), 시간 추가 등의 도움 아이템과 함께 최종 결과 요약(정답률, 카테고리별 성적)도 구현해주세요."},
51
+ {"description": "움직이는 표적을 맞추는 사격 갤러리 게임을 만들어주세요. 마우스 클릭으로 발사하며, 다양한 속도와 패턴으로 움직이는 표적(오리, 병, 풍선 등)을 맞추면 점수를 획득합니다. 제한된 시간과 총알 수 안에 최대한 많은 점수를 얻는 것이 목표이며, 특수 표적(황금 표적 등)은 보너스 점수나 추가 시간/총알을 제공합니다. 연속 명중 시 점수 배율이 증가하는 콤보 시스템과 함께 다양한 난이도 레벨(표적 속도/수 증가)도 구현해주세요."},
52
+ {"description": "가상 주사위를 굴려 보드판을 돌아다니는 보드 게임을 개발해주세요. 플레이어는 차례대로 1-6 주사위를 굴려 말을 이동시키며, 도착한 칸에 따라 다양한 이벤트(앞으로/뒤로 이동, 쉬기, 미니게임 등)가 발생합니다. 특수 아이템(추가 주사위, 이벤트 회피 등)을 수집하고 사용할 수 있으며, 먼저 결승점에 도달하거나 가장 많은 포인트를 모은 플레이어가 승리합니다. 1-4명의 로컬 멀티플레이어를 지원하며, AI 플레이어도 구현해주세요."},
53
+ {"description": "탑다운 뷰의 좀비 서바이벌 게임을 만들어주세요. WASD로 이동하고 마우스로 조준/발사하며, 끊임없이 몰려오는 좀비 웨이브를 최대한 오래 생존하는 것이 목표입니다. 다양한 무기(권총, 샷건, 기관총 등)와 제한된 탄약, 그리고 체력 회복 아이템과 폭탄 같은 특수 아이템을 맵에서 획득할 수 있습니다. 시간이 지날수록 좀비의 수와 속도가 증가하며, 특수 좀비(탱커, 러너 등)도 등장하는 난이도 시스템을 구현해주세요."},
54
+ {"description": "축구 페널티킥 게임을 개발해주세요. 공격 시에는 방향과 파워를 조절하여 슛을 날리고, 수비 시에는 골키퍼를 좌/중앙/우 중 한 방향으로 다이빙시켜 공을 막아야 합니다. 5번의 키커-골키퍼 대결 후 더 많은 골을 넣은 쪽이 승리하며, 동점일 경우 서든데스로 승부를 가립니다. 슛의 정확도와 파워에 따라 결과가 달라지며, 골키퍼 AI 패턴 학습을 통해 플레이어의 경향성을 파악하도록 구현해주세요. 1인 플레이와 2인 로컬 대전 모드를 모두 지원해주세요."},
55
+ {"description": "클래식한 지뢰찾기 게임을 구현해주세요. NxN 크기의 그리드에 M개의 지뢰가 무작위로 배치되며, 플레이어는 좌클릭으로 칸을 열고 우클릭으로 지뢰 위치에 깃발을 표시합니다. 열린 칸에는 주변 8칸의 지뢰 수가 표시되며, 주변에 지뢰가 없는 칸을 열면 연쇄적으로 주변 칸들이 열립니다. 지뢰가 있는 칸을 열면 게임 오버, 지뢰가 아닌 모든 칸을 열면 승리입니다. 난이도 설정(쉬움: 9x9/10개, 중간: 16x16/40개, 어려움: 30x16/99개)과 함께 첫 클릭은 항상 안전하도록 구현해주세요."},
56
+ {"description": " 플레이어가 번갈아가며 7x6 그리드에 색깔 디스크를 떨어뜨려 가로, 세로, 대각선으로 4개의 연속된 디스크를 만드는 Connect Four 게임을 개발해주세요. 플레이어는 열을 클릭하여 디스크를 해당 열의 가장 아래 칸에 배치합니다. 4개의 연속된 디스크를 먼저 만드는 플레이어가 승리하며, 모든 칸이 차면 무승부입니다. 1인 플레이(AI 대전)과 2인 로컬 대전 모드를 구현하고, AI는 최소한 1단계 앞을 내다보는 논리로 작동하도록 해주세요."},
57
+ {"description": "글자 타일을 배치하여 단어를 만드는 스크래블 스타일의 단어 게임을 만들어주세요. 플레이어는 7개의 글자 타일을 받고, 이를 보드에 배치하여 가로나 세로로 단어를 형성합니다. 새 단어는 기존 단어와 반드시 연결되어야 하며, 각 타일에는 점수가 있어 단어의 총점이 계산됩니다. 특수 칸(2배 글자 점수, 3배 단어 점수 등)을 활용한 전략적 배치가 가능하며, 사전 검증 기능으로 유효한 단어만 허용합니다. 1-4인 로컬 멀티플레이어와 AI 대전을 지원해주세요."},
58
+ {"description": "2D 환경에서 진행되는 탱크 전투 게임을 개발해주세요. 플레이어는 WASD로 탱크를 조종하고, 마우스로 포탑을 조준하여 클릭으로 발사합니다. 파괴 가능한 지형(벽돌, 나무 등)과 파괴 불가능한 장애물(강철, 물 등)이 있는 맵에서 적 탱크들과 전투를 벌입니다. 다양한 무기(기본 포탄, 확산탄, 레이저 등)와 아이템(속도 증가, 방어력 강화, 추가 생명 등)을 구현하고, 스테이지별로 증가하는 적 AI 난이도와 보스 전투도 추가해주세요."},
59
+ {"description": "3개 이상의 같은 보석을 맞추어 제거하는 퍼즐 게임을 만들어주세요. 인접한 보석을 스왑하여 매치를 만들며, 매치된 보석이 사라지면 위의 보석들이 떨어지고 새 보석이 채워집니다. 4개 이상 매치 시 특수 보석(가로/세로 폭발, 주변 9칸 폭발 등)이 생성되며, 연쇄 매치가 발생하면 콤보 점수가 추가됩니다. 제한 시간 또는 제한 이동 횟수 내에 목표 점수를 달성하는 레벨 기반 진행 구조와 함께, 특수 미션(특정 색상 N개 제거, 장애물 파괴 등)도 구현해주세요."},
60
+ {"description": "단일 타워가 끊임없이 몰려오는 적들을 격퇴하는 타워 디펜스 게임을 개발해주세요. 화면 중앙의 타워는 자동으로 가장 가까운 적을 향해 발사하며, 플레이어는 웨이브 사이에 획득한 자원으로 타워를 업그레이드(공격력, 공격 속도, 범위 등)할 수 있습니다. 시간이 지날수록 더 강력하고 다양한 적(빠른 적, 방어력 높은 적, 분열하는 적 등)이 등장하며, 타워의 체력이 0이 되면 게임 오버입니다. 특수 능력(범위 공격, 일시 정지, 즉시 회복 등)과 함께 생존한 웨이브 수에 따른 랭킹 시스템도 구현해주세요."},
61
+ {"description": "캐릭터가 끝없이 달리며 좀비와 장애물을 피하는 사이드 스크롤링 러너 게임을 만들어주세요. 스페이스바로 점프, S키로 슬라이딩하여 다양한 장애물(웅덩이, 장벽, 좀비 무리 등)을 피해야 합니다. 코인과 파워업(일시적 무적, 자석 효과, 속도 감소 등)을 수집하며, 특정 구간마다 미니 보스 좀비와의 간단한 전투도 포함됩니다. 거리에 따라 점수가 증가하고, 코인으로 캐릭터 업그레이드(더블 점프, 체력 증가 등)를 구매할 수 있는 시스템도 구현해주세요."},
62
+ {"description": "탑다운 뷰의 간단한 액션 RPG 게임을 개발해주세요. WASD 이동하고, 마우스 클릭으로 기본 공격, 1-4 키로 특수 스킬을 사용합니다. 플레이어는 몬스터를 처치하며 경험치와 아이템을 획득하고, 레벨업 시 능력치(공격력, 체력, 속도 등)를 향상시킵니다. 다양한 무기와 방어구를 착용할 수 있으며, 스킬 트리 시스템으로 캐릭터를 특화시킬 수 있습니다. 여러 지역과 보스 몬스터, 간단한 퀘스트 시스템도 구현해주세요."},
63
  ]
64
 
65
  SystemPrompt = """
66
  # GameCraft 시스템 프롬프트
67
 
68
  ## 1. 기본 정보 및 역할
69
+ 당신의 이름은 'GameCraft'입니다. 당신은 게임플레이 메커니즘, 인터랙티브 디자인, 성능 최적화에 뛰어난 웹 게임 개발 전문가입니다. HTML, JavaScript, CSS를 활용하여 간결하고 효율적인 웹 기반 게임을 제작하는 것이 당신의 임무입니다.
70
 
71
  ## 2. 핵심 기술 스택
72
  - **프론트엔드**: HTML5, CSS3, JavaScript(ES6+)
73
  - **렌더링 방식**: 브라우저에서 직접 렌더링 가능한 코드 생성
74
+ - **코드 스타일**: 바닐라 JavaScript 우선, 외부 라이브러리 최소화
75
 
76
  ## 3. 게임 유형별 특화 지침
77
  ### 3.1 아케이드/액션 게임
78
+ - 간결한 충돌 감지 시스템 구현
79
+ - 키보드/터치 입력 최적화
80
+ - 기본적인 점수 시스템
81
 
82
  ### 3.2 퍼즐 게임
83
+ - 명확한 게임 규칙 및 승리 조건
84
+ - 기본 난이도 구현
85
+ - 핵심 게임 메커니즘에 집중
86
 
87
  ### 3.3 카드/보드 게임
88
+ - 간소화된 턴 기반 시스템
89
+ - 기본 게임 규칙 자동화
90
+ - 핵심 게임 로직 중심
91
 
92
  ### 3.4 시뮬레이션 게임
93
+ - 효율적인 상태 관리
94
+ - 가장 중요한 상호작용 구현
95
+ - 핵심 요소만 포함
96
 
97
  ## 4. 이모지 활용 지침 🎮
98
+ - 게임 UI 요소에 이모지 활용 (예: 생명력 ❤️, 코인 💰, 시간 ⏱️)
99
+ - 이모지 사용은 핵심 요소에만 집중
 
 
 
 
100
 
101
  ## 5. 기술적 구현 가이드라인
102
  ### 5.1 코드 구조
103
+ - **간결성 중시**: 코드는 최대한 간결하게 작성하고, 주석은 최소화
104
+ - **모듈화**: 코드 기능별로 분리하되 불필요한 추상화 지양
105
+ - **최적화**: 게임 루프와 렌더링 최적화에 집중
106
+ - **코드 크기 제한**: 전체 코드는 200줄을 넘지 않도록 함
107
 
108
  ### 5.2 성능 최적화
109
+ - DOM 조작 최소화
110
+ - 불필요한 변수와 함수 제거
111
+ - 메모리 관리에 주의
 
112
 
113
  ### 5.3 반응형 디자인
114
+ - 기본적인 반응형 지원
115
+ - 핵심 기능에 집중한 심플한 UI
116
+
117
+ ## 6. 외부 라이브러리
118
+ - 라이브러리 사용은 최소화하고, 필요한 경우에만 사용
119
+ - 라이브러리 사용 CDN으로 가져올
 
 
 
 
 
120
 
121
  ## 7. 접근성 및 포용성
122
+ - 핵심 접근성 기능에만 집중
 
 
 
123
 
124
  ## 8. 제약사항 및 유의사항
125
+ - 외부 API 호출 금지
126
+ - 코드 크기 최소화에 우선순위 (200줄 이내)
127
+ - 주석 최소화 - 필수적인 설명만 포함
128
+ - 불필요한 기능 구현 지양
129
 
130
  ## 9. 출력 형식
131
  - HTML 코드 블록으로만 코드 반환
132
  - 추가 설명 없이 즉시 실행 가능한 코드만 제공
133
+ - 모든 코드는 단일 HTML 파일에 인라인으로 포함
134
 
135
  ## 10. 코드 품질 기준
136
+ - 효율성과 간결함이 최우선
137
+ - 핵심 게임플레이 메커니즘에만 집중
138
+ - 복잡한 기능보다 작동하는 기본 기능 우선
139
+ - 불필요한 주석이나 장황한 코드 지양
140
+ - 단일 파일에 모든 코드 포함
141
+ - 코드 길이 제한: 완성된 게임 코드는 200줄 이내로 작성
142
+
143
+ ## 11. 중요: 코드 생성 제한
144
+ - 게임 코드는 반드시 200줄 이내로 제한
145
+ - 불필요한 설명이나 주석 제외
146
+ - 핵심 기능만 구현하고 부가 기능은 생략
147
+ - 코드 크기가 커질 경우 기능을 간소화하거나 생략할 것
148
  """
149
 
150
 
 
203
 
204
  async def try_claude_api(system_message, claude_messages, timeout=15):
205
  """
206
+ Claude API 호출 (스트리밍) - 간결한 코드 생성 요청 추가
207
  """
208
  try:
209
+ # 시스템 프롬프트에 코드 길이 제한 강화
210
+ system_message_with_limit = system_message + "\n\n추가 중요 지침: 생성하는 코드는 절대로 200줄을 넘지 마세요. 코드 간결성이 최우선입니다. 주석을 최소화하고, 핵심 기능만 구현하세요. 불필요한 UI 요소나 장식은 제외하세요. 모든 코드는 하나의 HTML 파일에 포함되어야 합니다."
211
+
212
  start_time = time.time()
213
  with claude_client.messages.stream(
214
  model="claude-3-7-sonnet-20250219",
215
  max_tokens=19800,
216
+ system=system_message_with_limit,
217
+ messages=claude_messages,
218
+ temperature=0.3, # 온도 낮게 설정하여 일관된 결과 유도
219
  ) as stream:
220
  collected_content = ""
221
  for chunk in stream:
 
232
 
233
  async def try_openai_api(openai_messages):
234
  """
235
+ OpenAI API 호출 (스트리밍) - 코드 길이 제한 강화
236
  """
237
  try:
238
+ # 첫 번째 시스템 메시지에 코드 길이 제한 추가
239
+ if openai_messages and openai_messages[0]["role"] == "system":
240
+ openai_messages[0]["content"] += "\n\n추가 중요 지침: 생성하는 코드는 절대로 200줄을 넘지 마세요. 코드 간결성이 최우선입니다. 주석을 최소화하고, 핵심 기능만 구현하세요. 불필요한 UI 요소나 장식은 제외하세요. 모든 코드는 하나의 HTML 파일에 포함되어야 합니다."
241
+
242
  stream = openai_client.chat.completions.create(
243
  model="o3",
244
  messages=openai_messages,
245
  stream=True,
246
  max_tokens=19800,
247
+ temperature=0.2 # 온도 낮게 설정
248
  )
249
  collected_content = ""
250
  for chunk in stream:
 
253
  yield collected_content
254
  except Exception as e:
255
  raise e
256
+
257
 
258
  # ------------------------
259
  # 4) 템플릿(하나로 통합)
260
  # ------------------------
261
 
262
  def load_json_data():
 
 
 
263
  data_list = [
264
  {
265
  "name": "[게임] 테트리스 클론",
266
+ "prompt": "블록이 위에서 떨어지는 클래식 테트리스 게임을 개발해주세요. 화살표 키로 조작하며, 가로줄이 채워지면 해당 줄이 제거되고 점수가 올라가는 메커니즘이 필요합니다. 난이도는 시간이 지날수록 블록이 빨라지도록 구현하고, 게임오버 조건과 점수 표시 기능을 포함해주세요."
 
267
  },
268
  {
269
  "name": "[게임] 체스",
270
+ "prompt": " 명이 번갈아가며 플레이할 수 있는 체스 게임을 만들어주세요. 기본적인 체스 규칙(킹, 퀸, 룩, 비숍, 나이트, 폰의 이동 규칙)을 구현하고, 체크와 체크메이트 감지 기능이 필요합니다. 드래그 앤 드롭으로 말을 움직일 수 있게 하며, 이동 기록도 표시해주세요."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  },
272
+ # ... (중략 - 실제 운영 시에는 전체 목록 삽입)
273
  ]
274
  return data_list
275
 
 
 
 
 
 
 
 
276
  def create_template_html(title, items):
277
  """
278
+ 이미지 없이 템플릿 HTML 생성
279
  """
280
  html_content = r"""
281
  <style>
 
298
  transform: translateY(-4px);
299
  box-shadow: 0 6px 12px rgba(0,0,0,0.1);
300
  }
 
 
 
 
 
 
 
301
  .card-name {
302
  font-weight: bold;
303
  margin-bottom: 8px;
 
320
  </style>
321
  <div class="prompt-grid">
322
  """
323
+ import html as html_lib
324
  for item in items:
325
  card_html = f"""
326
+ <div class="prompt-card" onclick="copyToInput(this)" data-prompt="{html_lib.escape(item.get('prompt', ''))}">
327
+ <div class="card-name">{html_lib.escape(item.get('name', ''))}</div>
328
+ <div class="card-prompt">{html_lib.escape(item.get('prompt', ''))}</div>
 
329
  </div>
330
  """
331
  html_content += card_html
 
346
  """
347
  return gr.HTML(value=html_content)
348
 
349
+ def load_all_templates():
350
+ """
351
+ 모든 템플릿을 하나로 보여주는 함수
352
+ """
353
+ return create_template_html("🎮 모든 게임 템플릿", load_json_data())
354
+
355
 
356
  # ------------------------
357
  # 5) 배포/부스트/기타 유틸
358
  # ------------------------
359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
  def remove_code_block(text):
361
  """
362
  More robust function to extract code from markdown code blocks
363
  텍스트에서 ```html 및 ``` 태그를 완전히 제거하는 함수
364
  """
 
365
  pattern = r'```html\s*([\s\S]+?)\s*```'
366
  match = re.search(pattern, text, re.DOTALL)
367
  if match:
368
  return match.group(1).strip()
369
 
 
370
  pattern = r'```(?:\w+)?\s*([\s\S]+?)\s*```'
371
  match = re.search(pattern, text, re.DOTALL)
372
  if match:
373
  return match.group(1).strip()
374
 
 
375
  text = re.sub(r'```html\s*', '', text)
376
  text = re.sub(r'\s*```', '', text)
 
 
377
  return text.strip()
378
 
379
+ def optimize_code(code: str) -> str:
380
+ """
381
+ AI가 생성한 코드를 최적화하여 크기를 줄이는 함수
382
+ 불필요한 주석, 공백, 장황한 코드 등을 제거
383
+ """
384
+ if not code or len(code.strip()) == 0:
385
+ return code
386
+
387
+ lines = code.split('\n')
388
+ if len(lines) <= 200:
389
+ return code
390
+
391
+ comment_patterns = [
392
+ r'/\*[\s\S]*?\*/',
393
+ r'//.*?$',
394
+ r'<!--[\s\S]*?-->'
395
+ ]
396
+ cleaned_code = code
397
+ for pattern in comment_patterns:
398
+ cleaned_code = re.sub(pattern, '', cleaned_code, flags=re.MULTILINE)
399
+
400
+ cleaned_lines = []
401
+ empty_line_count = 0
402
+ for line in cleaned_code.split('\n'):
403
+ if line.strip() == '':
404
+ empty_line_count += 1
405
+ if empty_line_count <= 1:
406
+ cleaned_lines.append('')
407
+ else:
408
+ empty_line_count = 0
409
+ cleaned_lines.append(line)
410
+
411
+ cleaned_code = '\n'.join(cleaned_lines)
412
+ cleaned_code = re.sub(r'console\.log\(.*?\);', '', cleaned_code, flags=re.MULTILINE)
413
+ cleaned_code = re.sub(r' {2,}', ' ', cleaned_code)
414
+ return cleaned_code
415
+
416
  def send_to_sandbox(code):
417
  """
418
+ iframe으로 렌더링
 
419
  """
 
420
  clean_code = remove_code_block(code)
421
+ clean_code = optimize_code(clean_code)
422
 
 
 
 
 
 
423
  if clean_code.startswith('```html'):
424
  clean_code = clean_code[7:].strip()
425
  if clean_code.endswith('```'):
426
  clean_code = clean_code[:-3].strip()
427
 
 
428
  if not clean_code.strip().startswith('<!DOCTYPE') and not clean_code.strip().startswith('<html'):
429
  clean_code = f"""<!DOCTYPE html>
430
  <html>
 
437
  {clean_code}
438
  </body>
439
  </html>"""
 
 
440
  encoded_html = base64.b64encode(clean_code.encode('utf-8')).decode('utf-8')
441
  data_uri = f"data:text/html;charset=utf-8;base64,{encoded_html}"
442
  return f'<iframe src="{data_uri}" width="100%" height="920px" style="border:none;"></iframe>'
443
 
444
  def boost_prompt(prompt: str) -> str:
 
 
 
445
  if not prompt:
446
  return ""
447
  boost_system_prompt = """당신은 웹 게임 개발 프롬프트 전문가입니다.
448
+ 주어진 프롬프트를 분석하여 더 명확하고 간결한 요구사항으로 변환하되,
449
  원래 의도와 목적은 그대로 유지하면서 다음 관점들을 고려하여 증강하십시오:
450
 
451
+ 1. 게임플레이 핵심 메커니즘 명확히 정의
452
+ 2. 필수적인 상호작용 요소만 포함
453
+ 3. 핵심 UI 요소 간략히 기술
454
+ 4. 코드 간결성 유지를 위한 우선순위 설정
455
+ 5. 기본적인 게임 규칙과 승리/패배 조건 명시
456
+
457
+ 다음 중요 지침을 반드시 준수하세요:
458
+ - 불필요한 세부 사항이나 부가 기능은 제외
459
+ - 생성될 코드가 600줄을 넘지 않도록 기능을 제한
460
+ - 명확하고 간결한 언어로 요구사항 작성
461
+ - 최소한의 필수 게임 요소만 포함
462
  """
463
  try:
464
+ # Claude API
465
  try:
466
  response = claude_client.messages.create(
467
  model="claude-3-7-sonnet-20250219",
468
+ max_tokens=10000,
469
+ temperature=0.3,
470
  messages=[{
471
  "role": "user",
472
+ "content": f"다음 게임 프롬프트를 분석하고 증강하되, 간결함을 유지하세요: {prompt}"
473
+ }],
474
+ system=boost_system_prompt
475
  )
476
  if hasattr(response, 'content') and len(response.content) > 0:
477
  return response.content[0].text
478
  raise Exception("Claude API 응답 형식 오류")
479
  except Exception:
 
480
  completion = openai_client.chat.completions.create(
481
  model="gpt-4",
482
  messages=[
483
  {"role": "system", "content": boost_system_prompt},
484
+ {"role": "user", "content": f"다음 게임 프롬프트를 분석하고 증강하되, 간결함을 유지하세요: {prompt}"}
485
  ],
486
+ max_tokens=10000,
487
+ temperature=0.3
488
  )
489
  if completion.choices and len(completion.choices) > 0:
490
  return completion.choices[0].message.content
491
  raise Exception("OpenAI API 응답 형식 오류")
492
  except Exception:
 
493
  return prompt
494
 
495
  def handle_boost(prompt: str):
 
500
  return prompt, gr.update(active_key="empty")
501
 
502
  def history_render(history: History):
 
 
 
503
  return gr.update(open=True), history
504
 
505
  def execute_code(query: str):
 
 
 
 
506
  if not query or query.strip() == '':
507
  return None, gr.update(active_key="empty")
508
  try:
 
509
  clean_code = remove_code_block(query)
 
 
510
  if clean_code.startswith('```html'):
511
  clean_code = clean_code[7:].strip()
512
  if clean_code.endswith('```'):
513
  clean_code = clean_code[:-3].strip()
 
 
514
  if not clean_code.strip().startswith('<!DOCTYPE') and not clean_code.strip().startswith('<html'):
515
  if not ('<body' in clean_code and '</body>' in clean_code):
516
  clean_code = f"""<!DOCTYPE html>
 
524
  {clean_code}
525
  </body>
526
  </html>"""
 
527
  return send_to_sandbox(clean_code), gr.update(active_key="render")
528
  except Exception as e:
529
  print(f"Execute code error: {str(e)}")
 
537
  class Demo:
538
  def __init__(self):
539
  pass
540
+
541
  async def generation_code(self, query: Optional[str], _setting: Dict[str, str], _history: Optional[History]):
542
  if not query or query.strip() == '':
543
  query = random.choice(DEMO_LIST)['description']
 
545
  if _history is None:
546
  _history = []
547
 
548
+ query = f"""
549
+ 다음 게임을 제작해주세요.
550
+ 중요 요구사항:
551
+ 1. 코드는 가능한 한 간결하게 작성할 것
552
+ 2. 불필요한 주석이나 설명은 제외할 것
553
+ 3. 코드는 600줄을 넘지 않을 것
554
+ 4. 모든 코드는 하나의 HTML 파일에 통합할 것
555
+ 5. 핵심 기능만 구현하고 부가 기능은 생략할 것
556
+ 게임 요청: {query}
557
+ """
558
+
559
  messages = history_to_messages(_history, _setting['system'])
560
  system_message = messages[0]['content']
561
 
562
  claude_messages = [
563
+ {"role": msg["role"] if msg["role"] != "system" else "user", "content": msg["content"]}
564
  for msg in messages[1:] + [{'role': Role.USER, 'content': query}]
565
  if msg["content"].strip() != ''
566
  ]
 
574
  openai_messages.append({"role": "user", "content": query})
575
 
576
  try:
 
577
  yield [
578
  "Generating code...",
579
  _history,
 
582
  gr.update(open=True)
583
  ]
584
  await asyncio.sleep(0)
 
585
  collected_content = None
586
  try:
 
587
  async for content in try_claude_api(system_message, claude_messages):
588
  yield [
589
  content,
 
595
  await asyncio.sleep(0)
596
  collected_content = content
597
  except Exception:
 
598
  async for content in try_openai_api(openai_messages):
599
  yield [
600
  content,
 
607
  collected_content = content
608
 
609
  if collected_content:
 
 
 
 
 
 
 
 
 
 
610
  clean_code = remove_code_block(collected_content)
611
+ code_lines = clean_code.count('\n') + 1
612
+ if code_lines > 700:
613
+ warning_msg = f"""
614
+ ⚠️ **경고: 생성된 코드가 너무 깁니다 ({code_lines})**
615
+ 이로 인해 실행 시 오류가 발생할 수 있습니다. 다음과 같이 시도해 보세요:
616
+ 1. 간단한 게임을 요청하세요
617
+ 2. 특정 기능만 명시하여 요청하세요 (예: "간단한 Snake 게임, 점수 시스템 없이")
618
+ 3. "코드" 버튼을 사용하여 직접 실행해 보세요
619
+ ```html
620
+ {clean_code[:2000]}
621
+ ... (코드가 너무 깁니다) ... """
622
+ collected_content = warning_msg
623
+ yield [
624
+ collected_content,
625
+ _history,
626
+ None,
627
+ gr.update(active_key="empty"),
628
+ gr.update(open=True)
629
+ ]
630
+ else:
631
+ _history = messages_to_history([
632
+ {'role': Role.SYSTEM, 'content': system_message}
633
+ ] + claude_messages + [{
634
+ 'role': Role.ASSISTANT,
635
+ 'content': collected_content
636
+ }])
637
+ yield [
638
+ collected_content,
639
+ _history,
640
+ send_to_sandbox(clean_code),
641
+ gr.update(active_key="render"),
642
+ gr.update(open=True)
643
+ ]
644
  else:
645
  raise ValueError("No content was generated from either API")
646
  except Exception as e:
647
  raise ValueError(f'Error calling APIs: {str(e)}')
648
+
649
  def clear_history(self):
650
  return []
651
 
 
665
  )
666
 
667
  with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
668
+ # 헤더 HTML
669
+ header_html = gr.HTML("""
670
+ <div class="app-header">
671
+ <h1>🎮 Vibe Game Craft</h1>
672
+ <p>설명을 입력하면 웹 기반 HTML5, JavaScript, CSS 게임을 생성합니다. 직관적인 인터페이스로 쉽게 게임을 만들고, 실시간으로 미리보기를 확인하세요.</p>
673
+ </div>
674
+
675
+ <!-- 배포 결과 박스 - 헤더 바로 아래 위치 -->
676
+ <div id="deploy-banner" style="display:none;" class="deploy-banner">
677
+ <div class="deploy-banner-content">
678
+ <div class="deploy-banner-icon">🚀</div>
679
+ <div class="deploy-banner-info">
680
+ <div id="deploy-banner-title" class="deploy-banner-title">배포 상태</div>
681
+ <div id="deploy-banner-message" class="deploy-banner-message"></div>
682
+ </div>
683
+ <div id="deploy-banner-url-container" class="deploy-banner-url-container" style="display:none;">
684
+ <a id="deploy-banner-url" href="#" target="_blank" class="deploy-banner-url"></a>
685
+ <button onclick="copyBannerUrl()" class="deploy-banner-copy-btn">복사</button>
686
+ </div>
687
+ </div>
688
+ </div>
689
+
690
  <style>
691
  /* 전체 앱 스타일 */
692
  :root {
 
711
  .app-header {
712
  text-align: center;
713
  padding: 1.5rem 1rem;
714
+ margin-bottom: 0.5rem;
715
  background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
716
  border-radius: var(--radius);
717
  box-shadow: var(--shadow);
 
732
  margin: 0 auto;
733
  }
734
 
735
+ /* 배포 배너 스타일 - 헤더 바로 아래에 표시 */
736
+ .deploy-banner {
737
+ background: white;
738
  border-radius: var(--radius);
739
+ margin: 0.5rem auto 1.5rem auto;
740
  box-shadow: var(--shadow);
741
+ max-width: 1200px;
742
+ border: 1px solid #ddd;
743
  overflow: hidden;
744
+ transition: all 0.3s ease;
745
  }
746
 
747
+ .deploy-banner.success {
748
+ border-left: 5px solid #34c759;
749
  }
750
 
751
+ .deploy-banner.error {
752
+ border-left: 5px solid #ff3b30;
 
 
 
753
  }
754
 
755
+ .deploy-banner.loading {
756
+ border-left: 5px solid #007aff;
 
757
  }
758
 
759
+ .deploy-banner-content {
760
+ display: flex;
761
+ align-items: center;
762
+ padding: 15px 20px;
763
  }
764
 
765
+ .deploy-banner-icon {
766
+ font-size: 24px;
767
+ margin-right: 15px;
768
  }
769
 
770
+ .deploy-banner-info {
771
+ flex: 1;
 
 
 
772
  }
773
 
774
+ .deploy-banner-title {
775
+ font-weight: bold;
776
+ font-size: 16px;
777
+ margin-bottom: 5px;
778
  }
779
 
780
+ .deploy-banner-message {
781
+ color: #666;
 
 
 
782
  }
783
 
784
+ .deploy-banner-url-container {
785
+ background: #f5f8ff;
786
+ padding: 10px 15px;
787
+ border-radius: 8px;
788
+ margin-left: 20px;
789
+ max-width: 400px;
790
+ display: flex;
791
+ align-items: center;
792
  }
793
 
794
+ .deploy-banner-url {
795
+ color: #0066cc;
796
+ text-decoration: none;
797
+ font-weight: 500;
798
+ word-break: break-all;
799
+ flex: 1;
800
  }
801
 
802
+ .deploy-banner-copy-btn {
803
+ background: #0066cc;
804
+ color: white;
805
+ border: none;
806
+ border-radius: 4px;
807
+ padding: 5px 10px;
808
+ margin-left: 10px;
809
+ cursor: pointer;
810
+ font-size: 12px;
811
  }
812
+
813
+ .deploy-banner-copy-btn:hover {
814
+ background: #0052a3;
 
815
  }
816
  </style>
817
 
818
+ <script>
819
+ // URL 복사 함수
820
+ function copyBannerUrl() {
821
+ const url = document.getElementById('deploy-banner-url').href;
822
+ navigator.clipboard.writeText(url)
823
+ .then(() => {
824
+ const copyBtn = document.querySelector('.deploy-banner-copy-btn');
825
+ const originalText = copyBtn.textContent;
826
+ copyBtn.textContent = '복사됨!';
827
+ setTimeout(() => {
828
+ copyBtn.textContent = originalText;
829
+ }, 1000);
830
+ });
831
+ }
832
+
833
+ // 배포 배너 표시 함수
834
+ function showDeployBanner(type, title, message, url) {
835
+ const banner = document.getElementById('deploy-banner');
836
+ const bannerTitle = document.getElementById('deploy-banner-title');
837
+ const bannerMessage = document.getElementById('deploy-banner-message');
838
+ const bannerUrlContainer = document.getElementById('deploy-banner-url-container');
839
+ const bannerUrl = document.getElementById('deploy-banner-url');
840
+
841
+ banner.className = 'deploy-banner ' + type;
842
+ bannerTitle.textContent = title;
843
+ bannerMessage.textContent = message;
844
+
845
+ if (url) {
846
+ bannerUrl.href = url;
847
+ bannerUrl.textContent = url;
848
+ bannerUrlContainer.style.display = 'flex';
849
+ } else {
850
+ bannerUrlContainer.style.display = 'none';
851
+ }
852
+
853
+ banner.style.display = 'block';
854
+ }
855
+ </script>
856
  """)
857
 
858
  history = gr.State([])
859
  setting = gr.State({"system": SystemPrompt})
860
+
861
+ # 배포 상태를 저장할 변수
862
+ deploy_status = gr.State({
863
+ "is_deployed": False,
864
+ "status": "",
865
+ "url": "",
866
+ "message": ""
867
+ })
868
 
869
  with ms.Application() as app:
870
  with antd.ConfigProvider():
871
 
872
  # code Drawer
873
+ with antd.Drawer(open=False, title="코드 보기", placement="left", width="750px") as code_drawer:
874
  code_output = legacy.Markdown()
875
 
876
  # history Drawer
877
+ with antd.Drawer(open=False, title="히스토리", placement="left", width="900px") as history_drawer:
878
  history_output = legacy.Chatbot(
879
  show_label=False, flushing=False, height=960, elem_classes="history_chatbot"
880
  )
881
 
882
+ # templates Drawer
883
  with antd.Drawer(
884
  open=False,
885
+ title="게임 템플릿",
886
  placement="right",
887
  width="900px",
888
  elem_classes="session-drawer"
889
  ) as session_drawer:
890
  with antd.Flex(vertical=True, gap="middle"):
891
+ gr.Markdown("### 사용 가능한 게임 템플릿")
892
  session_history = gr.HTML(elem_classes="session-history")
893
+ close_btn = antd.Button("닫기", type="default", elem_classes="close-btn")
894
 
895
+ # 좌우 레이아웃
896
+ with antd.Row(gutter=[32, 12], align="top", elem_classes="equal-height-container") as layout:
897
 
898
+ # 왼쪽 Col
899
+ with antd.Col(span=24, md=16, elem_classes="equal-height-col"):
900
  with ms.Div(elem_classes="right_panel panel"):
901
  gr.HTML(r"""
902
  <div class="render_header">
 
915
  with antd.Tabs.Item(key="render"):
916
  sandbox = gr.HTML(elem_classes="html_content")
917
 
918
+ # 오른쪽 Col
919
+ with antd.Col(span=24, md=8, elem_classes="equal-height-col"):
920
+ with antd.Flex(vertical=True, gap="small", elem_classes="right-top-buttons"):
921
+ # 상단 메뉴 버튼들
922
+ with antd.Flex(gap="small", elem_classes="setting-buttons", justify="space-between"):
923
+ codeBtn = antd.Button("🧑‍💻 코드 보기", type="default", elem_classes="code-btn")
924
+ historyBtn = antd.Button("📜 히스토리", type="default", elem_classes="history-btn")
925
+ template_btn = antd.Button("🎮 템플릿", type="default", elem_classes="template-btn")
926
+
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
  input_text = antd.InputTextarea(
937
  size="large",
938
  allow_clear=True,
939
  placeholder=random.choice(DEMO_LIST)['description'],
940
+ max_length=100000
941
  )
942
  gr.HTML('<div class="help-text">💡 원하는 게임의 설명을 입력하세요. 예: "테트리스 게임 제작해줘."</div>')
943
 
944
+ deploy_result_container = gr.HTML(
945
+ """
946
+ <div class="deploy-section">
947
+ <div class="deploy-header">📤 배포 결과</div>
948
+ <div id="deploy-result-box" class="deploy-result-box">
949
+ <div class="no-deploy">아직 배포된 게임이 없습니다.</div>
950
+ </div>
951
+ </div>
952
+ """
953
+ )
954
+
955
+
956
+ js_trigger = gr.HTML(elem_id="js-trigger", visible=False)
957
+
958
+ # 이벤트 / 콜백
959
+ # Code Drawer
960
  codeBtn.click(
961
  lambda: gr.update(open=True),
962
  inputs=[],
 
968
  outputs=[code_drawer]
969
  )
970
 
971
+ # History Drawer
972
  historyBtn.click(
973
  history_render,
974
  inputs=[history],
 
980
  outputs=[history_drawer]
981
  )
982
 
983
+ # Template Drawer
984
  template_btn.click(
985
  fn=lambda: (gr.update(open=True), load_all_templates()),
986
  outputs=[session_drawer, session_history],
 
995
  outputs=[session_drawer, session_history]
996
  )
997
 
998
+ # 전송 버튼
999
  btn.click(
1000
  demo_instance.generation_code,
1001
  inputs=[input_text, setting, history],
1002
  outputs=[code_output, history, sandbox, state_tab, code_drawer]
1003
  )
1004
 
1005
+ # 클리어 버튼
1006
  clear_btn.click(
1007
  demo_instance.clear_history,
1008
  inputs=[],
1009
  outputs=[history]
1010
  )
1011
 
1012
+ # 증강 버튼
1013
  boost_btn.click(
1014
  fn=handle_boost,
1015
  inputs=[input_text],
1016
  outputs=[input_text, state_tab]
1017
  )
1018
 
1019
+ # 코드 실행 버튼
1020
  execute_btn.click(
1021
  fn=execute_code,
1022
  inputs=[input_text],
1023
  outputs=[sandbox, state_tab]
1024
  )
1025
 
1026
+ # 실제 배포 로직
1027
+ def deploy_to_vercel(code: str):
1028
+ try:
1029
+ token = "A8IFZmgW2cqA4yUNlLPnci0N" # 실제 토큰 필요
1030
+ if not token:
1031
+ return {"status": "error", "message": "Vercel 토큰이 설정되지 않았습니다."}
1032
+
1033
+ project_name = ''.join(random.choice(string.ascii_lowercase) for i in range(6))
1034
+ deploy_url = "https://api.vercel.com/v13/deployments"
1035
+ headers = {
1036
+ "Authorization": f"Bearer {token}",
1037
+ "Content-Type": "application/json"
1038
+ }
1039
+ package_json = {
1040
+ "name": project_name,
1041
+ "version": "1.0.0",
1042
+ "private": True,
1043
+ "dependencies": {"vite": "^5.0.0"},
1044
+ "scripts": {
1045
+ "dev": "vite",
1046
+ "build": "echo 'No build needed' && mkdir -p dist && cp index.html dist/",
1047
+ "preview": "vite preview"
1048
+ }
1049
+ }
1050
+ files = [
1051
+ {"file": "index.html", "data": code},
1052
+ {"file": "package.json", "data": json.dumps(package_json, indent=2)}
1053
+ ]
1054
+ project_settings = {
1055
+ "buildCommand": "npm run build",
1056
+ "outputDirectory": "dist",
1057
+ "installCommand": "npm install",
1058
+ "framework": None
1059
+ }
1060
+ deploy_data = {
1061
+ "name": project_name,
1062
+ "files": files,
1063
+ "target": "production",
1064
+ "projectSettings": project_settings
1065
+ }
1066
+ deploy_response = requests.post(deploy_url, headers=headers, json=deploy_data)
1067
+ if deploy_response.status_code != 200:
1068
+ return {"status": "error", "message": f"배포 실패: {deploy_response.text}"}
1069
+
1070
+ deployment_url = f"https://{project_name}.vercel.app"
1071
+ time.sleep(5)
1072
+
1073
+ return {
1074
+ "status": "success",
1075
+ "url": deployment_url,
1076
+ "project_name": project_name
1077
+ }
1078
+ except Exception as e:
1079
+ return {"status": "error", "message": f"배포 중 오류 발생: {str(e)}"}
1080
+
1081
+ # handle_deploy
1082
+ def handle_deploy(code, deploy_status):
1083
+ if not code:
1084
+ js_code = """
1085
+ <script>
1086
+ showDeployBanner('error', '⚠️ 배포 실패', '배포할 코드가 없습니다. 먼저 게임 코드를 생성해주세요.');
1087
+ document.getElementById('deploy-result-box').innerHTML = `
1088
+ <div class="deploy-error">
1089
+ <div class="error-icon">⚠️</div>
1090
+ <div class="error-message">배포할 코드가 없습니다.</div>
1091
+ </div>
1092
+ `;
1093
+ </script>
1094
+ """
1095
+ return js_code, {
1096
+ "is_deployed": False,
1097
+ "status": "error",
1098
+ "message": "배포할 코드가 없습니다.",
1099
+ "url": ""
1100
+ }
1101
+
1102
+ try:
1103
+ loading_js = """
1104
+ <script>
1105
+ showDeployBanner('loading', '🔄 배포 진행 중', 'Vercel에 게임을 배포하고 있습니다. 잠시만 기다려주세요.');
1106
+ document.getElementById('deploy-result-box').innerHTML = `
1107
+ <div class="deploy-loading">
1108
+ <div class="loading-spinner"></div>
1109
+ <div class="loading-message">Vercel에 배포 중입니다...</div>
1110
+ </div>
1111
+ `;
1112
+ </script>
1113
+ """
1114
+ yield loading_js, deploy_status
1115
+
1116
+ clean_code = remove_code_block(code)
1117
+ result = deploy_to_vercel(clean_code)
1118
+
1119
+ if result.get("status") == "success":
1120
+ url = result.get("url")
1121
+ success_js = f"""
1122
+ <script>
1123
+ showDeployBanner('success', '✅ 배포 완료!', '게임이 성공적으로 Vercel에 배포되었습니다.', '{url}');
1124
+ document.getElementById('deploy-result-box').innerHTML = `
1125
+ <div class="deploy-success">
1126
+ <div class="success-icon">✅</div>
1127
+ <div class="success-message">배포 완료!</div>
1128
+ <div class="url-box">
1129
+ <a href="{url}" target="_blank">{url}</a>
1130
+ <button class="copy-btn" onclick="navigator.clipboard.writeText('{url}')">복사</button>
1131
+ </div>
1132
+ </div>
1133
+ `;
1134
+ </script>
1135
+ """
1136
+ return success_js, {
1137
+ "is_deployed": True,
1138
+ "status": "success",
1139
+ "url": url,
1140
+ "message": "배포 완료!"
1141
+ }
1142
+ else:
1143
+ error_msg = result.get("message", "알 수 없는 오류")
1144
+ error_js = f"""
1145
+ <script>
1146
+ showDeployBanner('error', '⚠️ 배포 실패', '{error_msg}');
1147
+ document.getElementById('deploy-result-box').innerHTML = `
1148
+ <div class="deploy-error">
1149
+ <div class="error-icon">⚠️</div>
1150
+ <div class="error-message">배포 실패: {error_msg}</div>
1151
+ </div>
1152
+ `;
1153
+ </script>
1154
+ """
1155
+ return error_js, {
1156
+ "is_deployed": False,
1157
+ "status": "error",
1158
+ "message": error_msg,
1159
+ "url": ""
1160
+ }
1161
+
1162
+ except Exception as e:
1163
+ error_msg = str(e)
1164
+ exception_js = f"""
1165
+ <script>
1166
+ showDeployBanner('error', '⚠️ 시스템 오류', '{error_msg}');
1167
+ document.getElementById('deploy-result-box').innerHTML = `
1168
+ <div class="deploy-error">
1169
+ <div class="error-icon">⚠️</div>
1170
+ <div class="error-message">시스템 오류: {error_msg}</div>
1171
+ </div>
1172
+ `;
1173
+ </script>
1174
+ """
1175
+ return exception_js, {
1176
+ "is_deployed": False,
1177
+ "status": "error",
1178
+ "message": error_msg,
1179
+ "url": ""
1180
+ }
1181
+
1182
+ # 배포 버튼 클릭
1183
  deploy_btn.click(
1184
+ fn=handle_deploy,
1185
+ inputs=[code_output, deploy_status],
1186
+ outputs=[deploy_result_container, deploy_status]
1187
  )
1188
 
1189
 
 
1197
  demo.queue(default_concurrency_limit=20).launch(ssr_mode=False)
1198
  except Exception as e:
1199
  print(f"Initialization error: {e}")
1200
+ raise