Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
@@ -1,3 +1,5 @@
|
|
|
|
|
|
1 |
import os
|
2 |
import re
|
3 |
import random
|
@@ -10,6 +12,8 @@ import asyncio
|
|
10 |
import requests
|
11 |
import anthropic
|
12 |
import openai
|
|
|
|
|
13 |
|
14 |
from http import HTTPStatus
|
15 |
from typing import Dict, List, Optional, Tuple
|
@@ -20,11 +24,23 @@ import modelscope_studio.components.base as ms
|
|
20 |
import modelscope_studio.components.legacy as legacy
|
21 |
import modelscope_studio.components.antd as antd
|
22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
|
24 |
# ------------------------
|
25 |
# 1) DEMO_LIST 및 SystemPrompt
|
26 |
# ------------------------
|
27 |
|
|
|
28 |
DEMO_LIST = [
|
29 |
{"description": "블록이 위에서 떨어지는 클래식 테트리스 게임을 개발해주세요. 화살표 키로 조작하며, 가로줄이 채워지면 해당 줄이 제거되고 점수가 올라가는 메커니즘이 필요합니다. 난이도는 시간이 지날수록 블록이 빨라지도록 구현하고, 게임오버 조건과 점수 표시 기능을 포함해주세요."},
|
30 |
{"description": "두 명이 번갈아가며 플레이할 수 있는 체스 게임을 만들어주세요. 기본적인 체스 규칙(킹, 퀸, 룩, 비숍, 나이트, 폰의 이동 규칙)을 구현하고, 체크와 체크메이트 감지 기능이 필요합니다. 드래그 앤 드롭으로 말을 움직일 수 있게 하며, 이동 기록도 표시해주세요."},
|
@@ -62,6 +78,7 @@ DEMO_LIST = [
|
|
62 |
{"description": "탑다운 뷰의 간단한 액션 RPG 게임을 개발해주세요. WASD로 이동하고, 마우스 클릭으로 기본 공격, 1-4 키로 특수 스킬을 사용합니다. 플레이어는 몬스터를 처치하며 경험치와 아이템을 획득하고, 레벨업 시 능력치(공격력, 체력, 속도 등)를 향상시킵니다. 다양한 무기와 방어구를 착용할 수 있으며, 스킬 트리 시스템으로 캐릭터를 특화시킬 수 있습니다. 여러 지역과 보스 몬스터, 간단한 퀘스트 시스템도 구현해주세요."},
|
63 |
]
|
64 |
|
|
|
65 |
SystemPrompt = """
|
66 |
# GameCraft 시스템 프롬프트
|
67 |
|
@@ -203,11 +220,10 @@ openai_client = openai.OpenAI(api_key=YOUR_OPENAI_TOKEN)
|
|
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(
|
@@ -215,7 +231,7 @@ async def try_claude_api(system_message, claude_messages, timeout=15):
|
|
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:
|
@@ -235,16 +251,15 @@ async def try_openai_api(openai_messages):
|
|
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줄을 넘지 마세요. 코드 간결성이 최우선입니다.
|
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,14 +268,13 @@ async def try_openai_api(openai_messages):
|
|
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 |
-
# 실제 DEMO_LIST의 모든 항목을 반환하도록 수정
|
264 |
data_list = []
|
265 |
for item in DEMO_LIST:
|
266 |
data_list.append({
|
@@ -343,9 +357,6 @@ function copyToInput(card) {
|
|
343 |
return gr.HTML(value=html_content)
|
344 |
|
345 |
def load_all_templates():
|
346 |
-
"""
|
347 |
-
모든 템플릿을 하나로 보여주는 함수
|
348 |
-
"""
|
349 |
return create_template_html("🎮 모든 게임 템플릿", load_json_data())
|
350 |
|
351 |
|
@@ -354,10 +365,6 @@ def load_all_templates():
|
|
354 |
# ------------------------
|
355 |
|
356 |
def remove_code_block(text):
|
357 |
-
"""
|
358 |
-
More robust function to extract code from markdown code blocks
|
359 |
-
텍스트에서 ```html 및 ``` 태그를 완전히 제거하는 함수
|
360 |
-
"""
|
361 |
pattern = r'```html\s*([\s\S]+?)\s*```'
|
362 |
match = re.search(pattern, text, re.DOTALL)
|
363 |
if match:
|
@@ -373,10 +380,6 @@ def remove_code_block(text):
|
|
373 |
return text.strip()
|
374 |
|
375 |
def optimize_code(code: str) -> str:
|
376 |
-
"""
|
377 |
-
AI가 생성한 코드를 최적화하여 크기를 줄이는 함수
|
378 |
-
불필요한 주석, 공백, 장황한 코드 등을 제거
|
379 |
-
"""
|
380 |
if not code or len(code.strip()) == 0:
|
381 |
return code
|
382 |
|
@@ -410,9 +413,6 @@ def optimize_code(code: str) -> str:
|
|
410 |
return cleaned_code
|
411 |
|
412 |
def send_to_sandbox(code):
|
413 |
-
"""
|
414 |
-
iframe으로 렌더링
|
415 |
-
"""
|
416 |
clean_code = remove_code_block(code)
|
417 |
clean_code = optimize_code(clean_code)
|
418 |
|
@@ -457,7 +457,6 @@ def boost_prompt(prompt: str) -> str:
|
|
457 |
- 최소한의 필수 게임 요소만 포함
|
458 |
"""
|
459 |
try:
|
460 |
-
# Claude API
|
461 |
try:
|
462 |
response = claude_client.messages.create(
|
463 |
model="claude-3-7-sonnet-20250219",
|
@@ -645,20 +644,23 @@ class Demo:
|
|
645 |
def clear_history(self):
|
646 |
return []
|
647 |
|
|
|
648 |
####################################################
|
649 |
# 1) deploy_to_vercel 함수
|
650 |
####################################################
|
651 |
def deploy_to_vercel(code: str):
|
652 |
-
|
653 |
try:
|
654 |
if not code or len(code.strip()) < 10:
|
|
|
655 |
return "No code to deploy. (Code is empty)"
|
656 |
|
657 |
token = "A8IFZmgW2cqA4yUNlLPnci0N"
|
658 |
if not token:
|
|
|
659 |
return "Vercel token is not set."
|
660 |
|
661 |
-
project_name = ''.join(random.choice(string.ascii_lowercase) for
|
662 |
deploy_url = "https://api.vercel.com/v13/deployments"
|
663 |
headers = {
|
664 |
"Authorization": f"Bearer {token}",
|
@@ -693,18 +695,18 @@ def deploy_to_vercel(code: str):
|
|
693 |
"projectSettings": project_settings
|
694 |
}
|
695 |
|
696 |
-
|
697 |
deploy_response = requests.post(deploy_url, headers=headers, json=deploy_data)
|
698 |
-
|
699 |
|
700 |
if deploy_response.status_code != 200:
|
|
|
701 |
return f"Deployment failed: {deploy_response.text}"
|
702 |
|
703 |
deployment_url = f"https://{project_name}.vercel.app"
|
704 |
-
|
705 |
time.sleep(3)
|
706 |
|
707 |
-
# ★ 원본 코드 스타일: 심플 HTML (스크립트/iframe 없이)
|
708 |
return f"""
|
709 |
<div style="border:1px solid #34c759; padding:15px; border-radius:8px;">
|
710 |
<h3 style="margin:0; color:#34c759;">✅ Deployment complete!</h3>
|
@@ -718,44 +720,38 @@ def deploy_to_vercel(code: str):
|
|
718 |
"""
|
719 |
|
720 |
except Exception as e:
|
721 |
-
|
722 |
return f"Error during deployment: {str(e)}"
|
723 |
|
724 |
-
|
725 |
-
#
|
726 |
-
|
|
|
727 |
def handle_deploy_legacy(code):
|
728 |
-
|
729 |
if not code or len(code.strip()) < 10:
|
730 |
-
|
731 |
return "<div style='color:red;'>배포할 코드가 없습니다.</div>"
|
732 |
|
733 |
-
# remove_code_block 처리
|
734 |
clean_code = remove_code_block(code)
|
735 |
-
|
736 |
|
737 |
-
# 실제 배포
|
738 |
result_html = deploy_to_vercel(clean_code)
|
739 |
-
|
740 |
|
741 |
-
# iframe에 표시하기 위해 Base64로 인코딩
|
742 |
encoded_html = base64.b64encode(result_html.encode('utf-8')).decode()
|
743 |
data_uri = f"data:text/html;charset=utf-8;base64,{encoded_html}"
|
744 |
|
745 |
-
# Gradio 3.29 이하 버전에서는 unsafe_allow_html 미지원이므로,
|
746 |
-
# iframe 태그만 반환하여 script 실행.
|
747 |
iframe_html = f"""
|
748 |
<iframe src="{data_uri}"
|
749 |
style="width:100%; height:600px; border:none;"
|
750 |
sandbox="allow-scripts allow-same-origin allow-popups">
|
751 |
</iframe>
|
752 |
"""
|
753 |
-
|
754 |
return iframe_html
|
755 |
|
756 |
|
757 |
-
|
758 |
-
|
759 |
# ------------------------
|
760 |
# 8) Gradio / Modelscope UI 빌드
|
761 |
# ------------------------
|
@@ -1023,10 +1019,6 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
|
|
1023 |
gr.HTML('<div class="help-text">💡 원하는 게임의 설명을 입력하세요. 예: "테트리스 게임 제작해줘."</div>')
|
1024 |
|
1025 |
|
1026 |
-
##################################################################
|
1027 |
-
# 3) deploy_result_container = gr.HTML() (unsafe_allow_html = False)
|
1028 |
-
##################################################################
|
1029 |
-
# unsafe_allow_html 없이도 iframe 자체는 표시됨
|
1030 |
deploy_result_container = gr.HTML(
|
1031 |
"""
|
1032 |
<div class="deploy-section">
|
@@ -1039,6 +1031,7 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
|
|
1039 |
label="Deployment Result"
|
1040 |
)
|
1041 |
|
|
|
1042 |
# 이벤트 / 콜백
|
1043 |
# Code Drawer
|
1044 |
codeBtn.click(
|
@@ -1107,7 +1100,7 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
|
|
1107 |
outputs=[sandbox, state_tab]
|
1108 |
)
|
1109 |
|
1110 |
-
#
|
1111 |
deploy_btn.click(
|
1112 |
fn=lambda code: deploy_to_vercel(remove_code_block(code)) if code else "No code generated.",
|
1113 |
inputs=[code_output],
|
|
|
1 |
+
|
2 |
+
|
3 |
import os
|
4 |
import re
|
5 |
import random
|
|
|
12 |
import requests
|
13 |
import anthropic
|
14 |
import openai
|
15 |
+
import io
|
16 |
+
import logging
|
17 |
|
18 |
from http import HTTPStatus
|
19 |
from typing import Dict, List, Optional, Tuple
|
|
|
24 |
import modelscope_studio.components.legacy as legacy
|
25 |
import modelscope_studio.components.antd as antd
|
26 |
|
27 |
+
# === [1] 로거 설정 ===
|
28 |
+
log_stream = io.StringIO()
|
29 |
+
handler = logging.StreamHandler(log_stream)
|
30 |
+
logger = logging.getLogger()
|
31 |
+
logger.setLevel(logging.DEBUG) # 원하는 레벨로 설정
|
32 |
+
logger.addHandler(handler)
|
33 |
+
|
34 |
+
def get_logs():
|
35 |
+
"""StringIO에 쌓인 로그를 문자열로 반환"""
|
36 |
+
return log_stream.getvalue()
|
37 |
+
|
38 |
|
39 |
# ------------------------
|
40 |
# 1) DEMO_LIST 및 SystemPrompt
|
41 |
# ------------------------
|
42 |
|
43 |
+
|
44 |
DEMO_LIST = [
|
45 |
{"description": "블록이 위에서 떨어지는 클래식 테트리스 게임을 개발해주세요. 화살표 키로 조작하며, 가로줄이 채워지면 해당 줄이 제거되고 점수가 올라가는 메커니즘이 필요합니다. 난이도는 시간이 지날수록 블록이 빨라지도록 구현하고, 게임오버 조건과 점수 표시 기능을 포함해주세요."},
|
46 |
{"description": "두 명이 번갈아가며 플레이할 수 있는 체스 게임을 만들어주세요. 기본적인 체스 규칙(킹, 퀸, 룩, 비숍, 나이트, 폰의 이동 규칙)을 구현하고, 체크와 체크메이트 감지 기능이 필요합니다. 드래그 앤 드롭으로 말을 움직일 수 있게 하며, 이동 기록도 표시해주세요."},
|
|
|
78 |
{"description": "탑다운 뷰의 간단한 액션 RPG 게임을 개발해주세요. WASD로 이동하고, 마우스 클릭으로 기본 공격, 1-4 키로 특수 스킬을 사용합니다. 플레이어는 몬스터를 처치하며 경험치와 아이템을 획득하고, 레벨업 시 능력치(공격력, 체력, 속도 등)를 향상시킵니다. 다양한 무기와 방어구를 착용할 수 있으며, 스킬 트리 시스템으로 캐릭터를 특화시킬 수 있습니다. 여러 지역과 보스 몬스터, 간단한 퀘스트 시스템도 구현해주세요."},
|
79 |
]
|
80 |
|
81 |
+
|
82 |
SystemPrompt = """
|
83 |
# GameCraft 시스템 프롬프트
|
84 |
|
|
|
220 |
|
221 |
async def try_claude_api(system_message, claude_messages, timeout=15):
|
222 |
"""
|
223 |
+
Claude API 호출 (스트리밍)
|
224 |
"""
|
225 |
try:
|
226 |
+
system_message_with_limit = system_message + "\n\n추가 중요 지침: 생성하는 코드는 절대로 200줄을 넘지 마세요. 코드 간결성이 최우선입니다. 주석을 최소화하고, 핵심 기능만 구현하세요."
|
|
|
227 |
|
228 |
start_time = time.time()
|
229 |
with claude_client.messages.stream(
|
|
|
231 |
max_tokens=19800,
|
232 |
system=system_message_with_limit,
|
233 |
messages=claude_messages,
|
234 |
+
temperature=0.3,
|
235 |
) as stream:
|
236 |
collected_content = ""
|
237 |
for chunk in stream:
|
|
|
251 |
OpenAI API 호출 (스트리밍) - 코드 길이 제한 강화
|
252 |
"""
|
253 |
try:
|
|
|
254 |
if openai_messages and openai_messages[0]["role"] == "system":
|
255 |
+
openai_messages[0]["content"] += "\n\n추가 중요 지침: 생성하는 코드는 절대로 200줄을 넘지 마세요. 코드 간결성이 최우선입니다. 주석은 최소화하고, 핵심 기능만 구현하세요."
|
256 |
+
|
257 |
stream = openai_client.chat.completions.create(
|
258 |
model="o3",
|
259 |
messages=openai_messages,
|
260 |
stream=True,
|
261 |
max_tokens=19800,
|
262 |
+
temperature=0.2
|
263 |
)
|
264 |
collected_content = ""
|
265 |
for chunk in stream:
|
|
|
268 |
yield collected_content
|
269 |
except Exception as e:
|
270 |
raise e
|
271 |
+
|
272 |
|
273 |
# ------------------------
|
274 |
# 4) 템플릿(하나로 통합)
|
275 |
# ------------------------
|
276 |
|
277 |
def load_json_data():
|
|
|
278 |
data_list = []
|
279 |
for item in DEMO_LIST:
|
280 |
data_list.append({
|
|
|
357 |
return gr.HTML(value=html_content)
|
358 |
|
359 |
def load_all_templates():
|
|
|
|
|
|
|
360 |
return create_template_html("🎮 모든 게임 템플릿", load_json_data())
|
361 |
|
362 |
|
|
|
365 |
# ------------------------
|
366 |
|
367 |
def remove_code_block(text):
|
|
|
|
|
|
|
|
|
368 |
pattern = r'```html\s*([\s\S]+?)\s*```'
|
369 |
match = re.search(pattern, text, re.DOTALL)
|
370 |
if match:
|
|
|
380 |
return text.strip()
|
381 |
|
382 |
def optimize_code(code: str) -> str:
|
|
|
|
|
|
|
|
|
383 |
if not code or len(code.strip()) == 0:
|
384 |
return code
|
385 |
|
|
|
413 |
return cleaned_code
|
414 |
|
415 |
def send_to_sandbox(code):
|
|
|
|
|
|
|
416 |
clean_code = remove_code_block(code)
|
417 |
clean_code = optimize_code(clean_code)
|
418 |
|
|
|
457 |
- 최소한의 필수 게임 요소만 포함
|
458 |
"""
|
459 |
try:
|
|
|
460 |
try:
|
461 |
response = claude_client.messages.create(
|
462 |
model="claude-3-7-sonnet-20250219",
|
|
|
644 |
def clear_history(self):
|
645 |
return []
|
646 |
|
647 |
+
|
648 |
####################################################
|
649 |
# 1) deploy_to_vercel 함수
|
650 |
####################################################
|
651 |
def deploy_to_vercel(code: str):
|
652 |
+
logger.debug(f"[deploy_to_vercel] code 길이: {len(code) if code else 0}")
|
653 |
try:
|
654 |
if not code or len(code.strip()) < 10:
|
655 |
+
logger.info("[deploy_to_vercel] code가 짧아서 배포 불가")
|
656 |
return "No code to deploy. (Code is empty)"
|
657 |
|
658 |
token = "A8IFZmgW2cqA4yUNlLPnci0N"
|
659 |
if not token:
|
660 |
+
logger.error("[deploy_to_vercel] Vercel 토큰이 없습니다.")
|
661 |
return "Vercel token is not set."
|
662 |
|
663 |
+
project_name = ''.join(random.choice(string.ascii_lowercase) for _ in range(6))
|
664 |
deploy_url = "https://api.vercel.com/v13/deployments"
|
665 |
headers = {
|
666 |
"Authorization": f"Bearer {token}",
|
|
|
695 |
"projectSettings": project_settings
|
696 |
}
|
697 |
|
698 |
+
logger.debug("[deploy_to_vercel] Vercel API 요청 전송중...")
|
699 |
deploy_response = requests.post(deploy_url, headers=headers, json=deploy_data)
|
700 |
+
logger.debug(f"[deploy_to_vercel] 응답 status_code: {deploy_response.status_code}")
|
701 |
|
702 |
if deploy_response.status_code != 200:
|
703 |
+
logger.warning(f"[deploy_to_vercel] 배포 실패: {deploy_response.text}")
|
704 |
return f"Deployment failed: {deploy_response.text}"
|
705 |
|
706 |
deployment_url = f"https://{project_name}.vercel.app"
|
707 |
+
logger.info(f"[deploy_to_vercel] 배포 성공, URL={deployment_url}")
|
708 |
time.sleep(3)
|
709 |
|
|
|
710 |
return f"""
|
711 |
<div style="border:1px solid #34c759; padding:15px; border-radius:8px;">
|
712 |
<h3 style="margin:0; color:#34c759;">✅ Deployment complete!</h3>
|
|
|
720 |
"""
|
721 |
|
722 |
except Exception as e:
|
723 |
+
logger.exception("[deploy_to_vercel] 예외 발생")
|
724 |
return f"Error during deployment: {str(e)}"
|
725 |
|
726 |
+
|
727 |
+
# ------------------------
|
728 |
+
# (3) handle_deploy_legacy
|
729 |
+
# ------------------------
|
730 |
def handle_deploy_legacy(code):
|
731 |
+
logger.debug(f"[handle_deploy_legacy] code 길이: {len(code) if code else 0}")
|
732 |
if not code or len(code.strip()) < 10:
|
733 |
+
logger.info("[handle_deploy_legacy] 코드가 짧음.")
|
734 |
return "<div style='color:red;'>배포할 코드가 없습니다.</div>"
|
735 |
|
|
|
736 |
clean_code = remove_code_block(code)
|
737 |
+
logger.debug(f"[handle_deploy_legacy] remove_code_block 후 길이: {len(clean_code)}")
|
738 |
|
|
|
739 |
result_html = deploy_to_vercel(clean_code)
|
740 |
+
logger.debug(f"[handle_deploy_legacy] 배포 결과 HTML 길이: {len(result_html)}")
|
741 |
|
|
|
742 |
encoded_html = base64.b64encode(result_html.encode('utf-8')).decode()
|
743 |
data_uri = f"data:text/html;charset=utf-8;base64,{encoded_html}"
|
744 |
|
|
|
|
|
745 |
iframe_html = f"""
|
746 |
<iframe src="{data_uri}"
|
747 |
style="width:100%; height:600px; border:none;"
|
748 |
sandbox="allow-scripts allow-same-origin allow-popups">
|
749 |
</iframe>
|
750 |
"""
|
751 |
+
logger.debug("[handle_deploy_legacy] iframe_html 반환")
|
752 |
return iframe_html
|
753 |
|
754 |
|
|
|
|
|
755 |
# ------------------------
|
756 |
# 8) Gradio / Modelscope UI 빌드
|
757 |
# ------------------------
|
|
|
1019 |
gr.HTML('<div class="help-text">💡 원하는 게임의 설명을 입력하세요. 예: "테트리스 게임 제작해줘."</div>')
|
1020 |
|
1021 |
|
|
|
|
|
|
|
|
|
1022 |
deploy_result_container = gr.HTML(
|
1023 |
"""
|
1024 |
<div class="deploy-section">
|
|
|
1031 |
label="Deployment Result"
|
1032 |
)
|
1033 |
|
1034 |
+
|
1035 |
# 이벤트 / 콜백
|
1036 |
# Code Drawer
|
1037 |
codeBtn.click(
|
|
|
1100 |
outputs=[sandbox, state_tab]
|
1101 |
)
|
1102 |
|
1103 |
+
# 여기서 outputs=[deploy_result_container]가 핵심 수정
|
1104 |
deploy_btn.click(
|
1105 |
fn=lambda code: deploy_to_vercel(remove_code_block(code)) if code else "No code generated.",
|
1106 |
inputs=[code_output],
|