Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Update app-backup.py
Browse files- app-backup.py +160 -439
app-backup.py
CHANGED
@@ -10,6 +10,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 +22,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 +76,7 @@ DEMO_LIST = [
|
|
62 |
{"description": "탑다운 뷰의 간단한 액션 RPG 게임을 개발해주세요. WASD로 이동하고, 마우스 클릭으로 기본 공격, 1-4 키로 특수 스킬을 사용합니다. 플레이어는 몬스터를 처치하며 경험치와 아이템을 획득하고, 레벨업 시 능력치(공격력, 체력, 속도 등)를 향상시킵니다. 다양한 무기와 방어구를 착용할 수 있으며, 스킬 트리 시스템으로 캐릭터를 특화시킬 수 있습니다. 여러 지역과 보스 몬스터, 간단한 퀘스트 시스템도 구현해주세요."},
|
63 |
]
|
64 |
|
|
|
65 |
SystemPrompt = """
|
66 |
# GameCraft 시스템 프롬프트
|
67 |
|
@@ -203,11 +218,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 +229,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 +249,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 +266,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 +355,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 +363,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 +378,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 +411,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 +455,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",
|
@@ -646,10 +643,119 @@ class Demo:
|
|
646 |
return []
|
647 |
|
648 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
649 |
# ------------------------
|
650 |
-
#
|
651 |
# ------------------------
|
|
|
|
|
|
|
|
|
|
|
652 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
653 |
demo_instance = Demo()
|
654 |
theme = gr.themes.Soft(
|
655 |
primary_hue="blue",
|
@@ -661,221 +767,36 @@ theme = gr.themes.Soft(
|
|
661 |
)
|
662 |
|
663 |
with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
|
664 |
-
# 헤더 HTML
|
665 |
header_html = gr.HTML("""
|
666 |
<div class="app-header">
|
667 |
<h1>🎮 Vibe Game Craft</h1>
|
668 |
-
<p>설명을 입력하면 웹 기반 HTML5, JavaScript, CSS 게임을 생성합니다.
|
669 |
</div>
|
670 |
-
|
671 |
<!-- 배포 결과 박스 - 헤더 바로 아래 위치 -->
|
672 |
<div id="deploy-banner" style="display:none;" class="deploy-banner">
|
673 |
-
|
674 |
-
<div class="deploy-banner-icon">🚀</div>
|
675 |
-
<div class="deploy-banner-info">
|
676 |
-
<div id="deploy-banner-title" class="deploy-banner-title">배포 상태</div>
|
677 |
-
<div id="deploy-banner-message" class="deploy-banner-message"></div>
|
678 |
-
</div>
|
679 |
-
<div id="deploy-banner-url-container" class="deploy-banner-url-container" style="display:none;">
|
680 |
-
<a id="deploy-banner-url" href="#" target="_blank" class="deploy-banner-url"></a>
|
681 |
-
<button onclick="copyBannerUrl()" class="deploy-banner-copy-btn">복사</button>
|
682 |
-
</div>
|
683 |
-
</div>
|
684 |
</div>
|
685 |
-
|
686 |
<style>
|
687 |
-
/*
|
688 |
-
:root {
|
689 |
-
--primary-color: #9c89b8;
|
690 |
-
--secondary-color: #f0a6ca;
|
691 |
-
--accent-color: #b8bedd;
|
692 |
-
--background-color: #f9f7fd;
|
693 |
-
--panel-color: #ffffff;
|
694 |
-
--text-color: #3a3042;
|
695 |
-
--button-hover: #efc3e6;
|
696 |
-
--shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
|
697 |
-
--radius: 12px;
|
698 |
-
}
|
699 |
-
|
700 |
-
body {
|
701 |
-
background-color: var(--background-color);
|
702 |
-
color: var(--text-color);
|
703 |
-
font-family: 'Poppins', sans-serif;
|
704 |
-
}
|
705 |
-
|
706 |
-
/* 헤더 스타일 */
|
707 |
-
.app-header {
|
708 |
-
text-align: center;
|
709 |
-
padding: 1.5rem 1rem;
|
710 |
-
margin-bottom: 0.5rem;
|
711 |
-
background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
|
712 |
-
border-radius: var(--radius);
|
713 |
-
box-shadow: var(--shadow);
|
714 |
-
color: white;
|
715 |
-
}
|
716 |
-
|
717 |
-
.app-header h1 {
|
718 |
-
font-size: 2.5rem;
|
719 |
-
font-weight: 700;
|
720 |
-
margin-bottom: 0.5rem;
|
721 |
-
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
722 |
-
}
|
723 |
-
|
724 |
-
.app-header p {
|
725 |
-
font-size: 1.1rem;
|
726 |
-
opacity: 0.9;
|
727 |
-
max-width: 800px;
|
728 |
-
margin: 0 auto;
|
729 |
-
}
|
730 |
-
|
731 |
-
/* 배포 배너 스타일 - 헤더 바로 아래에 표시 */
|
732 |
-
.deploy-banner {
|
733 |
-
background: white;
|
734 |
-
border-radius: var(--radius);
|
735 |
-
margin: 0.5rem auto 1.5rem auto;
|
736 |
-
box-shadow: var(--shadow);
|
737 |
-
max-width: 1200px;
|
738 |
-
border: 1px solid #ddd;
|
739 |
-
overflow: hidden;
|
740 |
-
transition: all 0.3s ease;
|
741 |
-
}
|
742 |
-
|
743 |
-
.deploy-banner.success {
|
744 |
-
border-left: 5px solid #34c759;
|
745 |
-
}
|
746 |
-
|
747 |
-
.deploy-banner.error {
|
748 |
-
border-left: 5px solid #ff3b30;
|
749 |
-
}
|
750 |
-
|
751 |
-
.deploy-banner.loading {
|
752 |
-
border-left: 5px solid #007aff;
|
753 |
-
}
|
754 |
-
|
755 |
-
.deploy-banner-content {
|
756 |
-
display: flex;
|
757 |
-
align-items: center;
|
758 |
-
padding: 15px 20px;
|
759 |
-
}
|
760 |
-
|
761 |
-
.deploy-banner-icon {
|
762 |
-
font-size: 24px;
|
763 |
-
margin-right: 15px;
|
764 |
-
}
|
765 |
-
|
766 |
-
.deploy-banner-info {
|
767 |
-
flex: 1;
|
768 |
-
}
|
769 |
-
|
770 |
-
.deploy-banner-title {
|
771 |
-
font-weight: bold;
|
772 |
-
font-size: 16px;
|
773 |
-
margin-bottom: 5px;
|
774 |
-
}
|
775 |
-
|
776 |
-
.deploy-banner-message {
|
777 |
-
color: #666;
|
778 |
-
}
|
779 |
-
|
780 |
-
.deploy-banner-url-container {
|
781 |
-
background: #f5f8ff;
|
782 |
-
padding: 10px 15px;
|
783 |
-
border-radius: 8px;
|
784 |
-
margin-left: 20px;
|
785 |
-
max-width: 400px;
|
786 |
-
display: flex;
|
787 |
-
align-items: center;
|
788 |
-
}
|
789 |
-
|
790 |
-
.deploy-banner-url {
|
791 |
-
color: #0066cc;
|
792 |
-
text-decoration: none;
|
793 |
-
font-weight: 500;
|
794 |
-
word-break: break-all;
|
795 |
-
flex: 1;
|
796 |
-
}
|
797 |
-
|
798 |
-
.deploy-banner-copy-btn {
|
799 |
-
background: #0066cc;
|
800 |
-
color: white;
|
801 |
-
border: none;
|
802 |
-
border-radius: 4px;
|
803 |
-
padding: 5px 10px;
|
804 |
-
margin-left: 10px;
|
805 |
-
cursor: pointer;
|
806 |
-
font-size: 12px;
|
807 |
-
}
|
808 |
-
|
809 |
-
.deploy-banner-copy-btn:hover {
|
810 |
-
background: #0052a3;
|
811 |
-
}
|
812 |
</style>
|
813 |
-
|
814 |
<script>
|
815 |
-
|
816 |
-
function copyBannerUrl() {
|
817 |
-
const url = document.getElementById('deploy-banner-url').href;
|
818 |
-
navigator.clipboard.writeText(url)
|
819 |
-
.then(() => {
|
820 |
-
const copyBtn = document.querySelector('.deploy-banner-copy-btn');
|
821 |
-
const originalText = copyBtn.textContent;
|
822 |
-
copyBtn.textContent = '복사됨!';
|
823 |
-
setTimeout(() => {
|
824 |
-
copyBtn.textContent = originalText;
|
825 |
-
}, 1000);
|
826 |
-
});
|
827 |
-
}
|
828 |
-
|
829 |
-
// 배포 배너 표시 함수
|
830 |
-
function showDeployBanner(type, title, message, url) {
|
831 |
-
const banner = document.getElementById('deploy-banner');
|
832 |
-
const bannerTitle = document.getElementById('deploy-banner-title');
|
833 |
-
const bannerMessage = document.getElementById('deploy-banner-message');
|
834 |
-
const bannerUrlContainer = document.getElementById('deploy-banner-url-container');
|
835 |
-
const bannerUrl = document.getElementById('deploy-banner-url');
|
836 |
-
|
837 |
-
banner.className = 'deploy-banner ' + type;
|
838 |
-
bannerTitle.textContent = title;
|
839 |
-
bannerMessage.textContent = message;
|
840 |
-
|
841 |
-
if (url) {
|
842 |
-
bannerUrl.href = url;
|
843 |
-
bannerUrl.textContent = url;
|
844 |
-
bannerUrlContainer.style.display = 'flex';
|
845 |
-
} else {
|
846 |
-
bannerUrlContainer.style.display = 'none';
|
847 |
-
}
|
848 |
-
|
849 |
-
banner.style.display = 'block';
|
850 |
-
}
|
851 |
</script>
|
852 |
""")
|
853 |
|
854 |
history = gr.State([])
|
855 |
setting = gr.State({"system": SystemPrompt})
|
856 |
-
|
857 |
-
# 배포 상태를 저장할 변수
|
858 |
-
deploy_status = gr.State({
|
859 |
-
"is_deployed": False,
|
860 |
-
"status": "",
|
861 |
-
"url": "",
|
862 |
-
"message": ""
|
863 |
-
})
|
864 |
|
865 |
with ms.Application() as app:
|
866 |
with antd.ConfigProvider():
|
867 |
|
868 |
-
# code Drawer
|
869 |
with antd.Drawer(open=False, title="코드 보기", placement="left", width="750px") as code_drawer:
|
870 |
code_output = legacy.Markdown()
|
871 |
|
872 |
-
# history Drawer
|
873 |
with antd.Drawer(open=False, title="히스토리", placement="left", width="900px") as history_drawer:
|
874 |
-
history_output = legacy.Chatbot(
|
875 |
-
show_label=False, flushing=False, height=960, elem_classes="history_chatbot"
|
876 |
-
)
|
877 |
|
878 |
-
# templates Drawer
|
879 |
with antd.Drawer(
|
880 |
open=False,
|
881 |
title="게임 템플릿",
|
@@ -888,7 +809,6 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
|
|
888 |
session_history = gr.HTML(elem_classes="session-history")
|
889 |
close_btn = antd.Button("닫기", type="default", elem_classes="close-btn")
|
890 |
|
891 |
-
# 좌우 레이아웃
|
892 |
with antd.Row(gutter=[32, 12], align="top", elem_classes="equal-height-container") as layout:
|
893 |
|
894 |
# 왼쪽 Col
|
@@ -896,31 +816,25 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
|
|
896 |
with ms.Div(elem_classes="right_panel panel"):
|
897 |
gr.HTML(r"""
|
898 |
<div class="render_header">
|
899 |
-
<span class="header_btn"></span>
|
900 |
-
<span class="header_btn"></span>
|
901 |
-
<span class="header_btn"></span>
|
902 |
</div>
|
903 |
""")
|
904 |
with antd.Tabs(active_key="empty", render_tab_bar="() => null") as state_tab:
|
905 |
with antd.Tabs.Item(key="empty"):
|
906 |
empty = antd.Empty(description="게임을 만들려면 설명을 입력하세요", elem_classes="right_content")
|
907 |
with antd.Tabs.Item(key="loading"):
|
908 |
-
loading = antd.Spin(
|
909 |
-
True, tip="게임 코드 생성 중...", size="large", elem_classes="right_content"
|
910 |
-
)
|
911 |
with antd.Tabs.Item(key="render"):
|
912 |
sandbox = gr.HTML(elem_classes="html_content")
|
913 |
|
914 |
# 오른쪽 Col
|
915 |
with antd.Col(span=24, md=8, elem_classes="equal-height-col"):
|
916 |
with antd.Flex(vertical=True, gap="small", elem_classes="right-top-buttons"):
|
917 |
-
# 상단 메뉴 버튼들
|
918 |
with antd.Flex(gap="small", elem_classes="setting-buttons", justify="space-between"):
|
919 |
codeBtn = antd.Button("🧑💻 코드 보기", type="default", elem_classes="code-btn")
|
920 |
historyBtn = antd.Button("📜 히스토리", type="default", elem_classes="history-btn")
|
921 |
template_btn = antd.Button("🎮 템플릿", type="default", elem_classes="template-btn")
|
922 |
|
923 |
-
# 액션 버튼들
|
924 |
with antd.Flex(gap="small", justify="space-between", elem_classes="action-buttons"):
|
925 |
btn = antd.Button("전송", type="primary", size="large", elem_classes="send-btn")
|
926 |
boost_btn = antd.Button("증강", type="default", size="large", elem_classes="boost-btn")
|
@@ -937,44 +851,19 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
|
|
937 |
)
|
938 |
gr.HTML('<div class="help-text">💡 원하는 게임의 설명을 입력하세요. 예: "테트리스 게임 제작해줘."</div>')
|
939 |
|
940 |
-
|
941 |
-
|
942 |
-
|
943 |
-
|
944 |
-
|
945 |
-
<div class="no-deploy">아직 배포된 게임이 없습니다.</div>
|
946 |
-
</div>
|
947 |
-
</div>
|
948 |
-
"""
|
949 |
-
)
|
950 |
-
|
951 |
-
|
952 |
-
js_trigger = gr.HTML(elem_id="js-trigger", visible=False)
|
953 |
-
|
954 |
-
# 이벤트 / 콜백
|
955 |
-
# Code Drawer
|
956 |
-
codeBtn.click(
|
957 |
-
lambda: gr.update(open=True),
|
958 |
-
inputs=[],
|
959 |
-
outputs=[code_drawer]
|
960 |
-
)
|
961 |
-
code_drawer.close(
|
962 |
-
lambda: gr.update(open=False),
|
963 |
-
inputs=[],
|
964 |
-
outputs=[code_drawer]
|
965 |
-
)
|
966 |
|
967 |
-
#
|
968 |
-
|
969 |
-
|
970 |
-
|
971 |
-
|
972 |
-
)
|
973 |
-
history_drawer.close(
|
974 |
-
lambda: gr.update(open=False),
|
975 |
-
inputs=[],
|
976 |
-
outputs=[history_drawer]
|
977 |
-
)
|
978 |
|
979 |
# Template Drawer
|
980 |
template_btn.click(
|
@@ -982,14 +871,8 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
|
|
982 |
outputs=[session_drawer, session_history],
|
983 |
queue=False
|
984 |
)
|
985 |
-
session_drawer.close(
|
986 |
-
|
987 |
-
outputs=[session_drawer, session_history]
|
988 |
-
)
|
989 |
-
close_btn.click(
|
990 |
-
lambda: (gr.update(open=False), gr.HTML("")),
|
991 |
-
outputs=[session_drawer, session_history]
|
992 |
-
)
|
993 |
|
994 |
# 전송 버튼
|
995 |
btn.click(
|
@@ -999,11 +882,7 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
|
|
999 |
)
|
1000 |
|
1001 |
# 클리어 버튼
|
1002 |
-
clear_btn.click(
|
1003 |
-
demo_instance.clear_history,
|
1004 |
-
inputs=[],
|
1005 |
-
outputs=[history]
|
1006 |
-
)
|
1007 |
|
1008 |
# 증강 버튼
|
1009 |
boost_btn.click(
|
@@ -1019,174 +898,16 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
|
|
1019 |
outputs=[sandbox, state_tab]
|
1020 |
)
|
1021 |
|
1022 |
-
#
|
1023 |
-
def deploy_to_vercel(code: str):
|
1024 |
-
try:
|
1025 |
-
token = "A8IFZmgW2cqA4yUNlLPnci0N" # 실제 토큰 필요
|
1026 |
-
if not token:
|
1027 |
-
return {"status": "error", "message": "Vercel 토큰이 설정되지 않았습니다."}
|
1028 |
-
|
1029 |
-
project_name = ''.join(random.choice(string.ascii_lowercase) for i in range(6))
|
1030 |
-
deploy_url = "https://api.vercel.com/v13/deployments"
|
1031 |
-
headers = {
|
1032 |
-
"Authorization": f"Bearer {token}",
|
1033 |
-
"Content-Type": "application/json"
|
1034 |
-
}
|
1035 |
-
package_json = {
|
1036 |
-
"name": project_name,
|
1037 |
-
"version": "1.0.0",
|
1038 |
-
"private": True,
|
1039 |
-
"dependencies": {"vite": "^5.0.0"},
|
1040 |
-
"scripts": {
|
1041 |
-
"dev": "vite",
|
1042 |
-
"build": "echo 'No build needed' && mkdir -p dist && cp index.html dist/",
|
1043 |
-
"preview": "vite preview"
|
1044 |
-
}
|
1045 |
-
}
|
1046 |
-
files = [
|
1047 |
-
{"file": "index.html", "data": code},
|
1048 |
-
{"file": "package.json", "data": json.dumps(package_json, indent=2)}
|
1049 |
-
]
|
1050 |
-
project_settings = {
|
1051 |
-
"buildCommand": "npm run build",
|
1052 |
-
"outputDirectory": "dist",
|
1053 |
-
"installCommand": "npm install",
|
1054 |
-
"framework": None
|
1055 |
-
}
|
1056 |
-
deploy_data = {
|
1057 |
-
"name": project_name,
|
1058 |
-
"files": files,
|
1059 |
-
"target": "production",
|
1060 |
-
"projectSettings": project_settings
|
1061 |
-
}
|
1062 |
-
deploy_response = requests.post(deploy_url, headers=headers, json=deploy_data)
|
1063 |
-
if deploy_response.status_code != 200:
|
1064 |
-
return {"status": "error", "message": f"배포 실패: {deploy_response.text}"}
|
1065 |
-
|
1066 |
-
deployment_url = f"https://{project_name}.vercel.app"
|
1067 |
-
time.sleep(5)
|
1068 |
-
|
1069 |
-
return {
|
1070 |
-
"status": "success",
|
1071 |
-
"url": deployment_url,
|
1072 |
-
"project_name": project_name
|
1073 |
-
}
|
1074 |
-
except Exception as e:
|
1075 |
-
return {"status": "error", "message": f"배포 중 오류 발생: {str(e)}"}
|
1076 |
-
|
1077 |
-
# handle_deploy
|
1078 |
-
def handle_deploy(code, deploy_status):
|
1079 |
-
if not code:
|
1080 |
-
js_code = """
|
1081 |
-
<script>
|
1082 |
-
showDeployBanner('error', '⚠️ 배포 실패', '배포할 코드가 없습니다. 먼저 게임 코드를 생성해주세요.');
|
1083 |
-
document.getElementById('deploy-result-box').innerHTML = `
|
1084 |
-
<div class="deploy-error">
|
1085 |
-
<div class="error-icon">⚠️</div>
|
1086 |
-
<div class="error-message">배포할 코드가 없습니다.</div>
|
1087 |
-
</div>
|
1088 |
-
`;
|
1089 |
-
</script>
|
1090 |
-
"""
|
1091 |
-
return js_code, {
|
1092 |
-
"is_deployed": False,
|
1093 |
-
"status": "error",
|
1094 |
-
"message": "배포할 코드가 없습니다.",
|
1095 |
-
"url": ""
|
1096 |
-
}
|
1097 |
-
|
1098 |
-
try:
|
1099 |
-
loading_js = """
|
1100 |
-
<script>
|
1101 |
-
showDeployBanner('loading', '🔄 배포 진행 중', 'Vercel에 게임을 배포하고 있습니다. 잠시만 기다려주세요.');
|
1102 |
-
document.getElementById('deploy-result-box').innerHTML = `
|
1103 |
-
<div class="deploy-loading">
|
1104 |
-
<div class="loading-spinner"></div>
|
1105 |
-
<div class="loading-message">Vercel에 배포 중입니다...</div>
|
1106 |
-
</div>
|
1107 |
-
`;
|
1108 |
-
</script>
|
1109 |
-
"""
|
1110 |
-
yield loading_js, deploy_status
|
1111 |
-
|
1112 |
-
clean_code = remove_code_block(code)
|
1113 |
-
result = deploy_to_vercel(clean_code)
|
1114 |
-
|
1115 |
-
if result.get("status") == "success":
|
1116 |
-
url = result.get("url")
|
1117 |
-
success_js = f"""
|
1118 |
-
<script>
|
1119 |
-
showDeployBanner('success', '✅ 배포 완료!', '게임이 성공적으로 Vercel에 배포되었습니다.', '{url}');
|
1120 |
-
document.getElementById('deploy-result-box').innerHTML = `
|
1121 |
-
<div class="deploy-success">
|
1122 |
-
<div class="success-icon">✅</div>
|
1123 |
-
<div class="success-message">배포 완료!</div>
|
1124 |
-
<div class="url-box">
|
1125 |
-
<a href="{url}" target="_blank">{url}</a>
|
1126 |
-
<button class="copy-btn" onclick="navigator.clipboard.writeText('{url}')">복사</button>
|
1127 |
-
</div>
|
1128 |
-
</div>
|
1129 |
-
`;
|
1130 |
-
</script>
|
1131 |
-
"""
|
1132 |
-
return success_js, {
|
1133 |
-
"is_deployed": True,
|
1134 |
-
"status": "success",
|
1135 |
-
"url": url,
|
1136 |
-
"message": "배포 완료!"
|
1137 |
-
}
|
1138 |
-
else:
|
1139 |
-
error_msg = result.get("message", "알 수 없는 오류")
|
1140 |
-
error_js = f"""
|
1141 |
-
<script>
|
1142 |
-
showDeployBanner('error', '⚠️ 배포 실패', '{error_msg}');
|
1143 |
-
document.getElementById('deploy-result-box').innerHTML = `
|
1144 |
-
<div class="deploy-error">
|
1145 |
-
<div class="error-icon">⚠️</div>
|
1146 |
-
<div class="error-message">배포 실패: {error_msg}</div>
|
1147 |
-
</div>
|
1148 |
-
`;
|
1149 |
-
</script>
|
1150 |
-
"""
|
1151 |
-
return error_js, {
|
1152 |
-
"is_deployed": False,
|
1153 |
-
"status": "error",
|
1154 |
-
"message": error_msg,
|
1155 |
-
"url": ""
|
1156 |
-
}
|
1157 |
-
|
1158 |
-
except Exception as e:
|
1159 |
-
error_msg = str(e)
|
1160 |
-
exception_js = f"""
|
1161 |
-
<script>
|
1162 |
-
showDeployBanner('error', '⚠️ 시스템 오류', '{error_msg}');
|
1163 |
-
document.getElementById('deploy-result-box').innerHTML = `
|
1164 |
-
<div class="deploy-error">
|
1165 |
-
<div class="error-icon">⚠️</div>
|
1166 |
-
<div class="error-message">시스템 오류: {error_msg}</div>
|
1167 |
-
</div>
|
1168 |
-
`;
|
1169 |
-
</script>
|
1170 |
-
"""
|
1171 |
-
return exception_js, {
|
1172 |
-
"is_deployed": False,
|
1173 |
-
"status": "error",
|
1174 |
-
"message": error_msg,
|
1175 |
-
"url": ""
|
1176 |
-
}
|
1177 |
-
|
1178 |
-
# 배포 버튼 클릭
|
1179 |
deploy_btn.click(
|
1180 |
-
fn=
|
1181 |
-
inputs=[code_output
|
1182 |
-
outputs=[deploy_result_container
|
1183 |
)
|
1184 |
|
1185 |
-
|
1186 |
# ------------------------
|
1187 |
-
#
|
1188 |
# ------------------------
|
1189 |
-
|
1190 |
if __name__ == "__main__":
|
1191 |
try:
|
1192 |
demo_instance = Demo()
|
|
|
10 |
import requests
|
11 |
import anthropic
|
12 |
import openai
|
13 |
+
import io
|
14 |
+
import logging
|
15 |
|
16 |
from http import HTTPStatus
|
17 |
from typing import Dict, List, Optional, Tuple
|
|
|
22 |
import modelscope_studio.components.legacy as legacy
|
23 |
import modelscope_studio.components.antd as antd
|
24 |
|
25 |
+
# === [1] 로거 설정 ===
|
26 |
+
log_stream = io.StringIO()
|
27 |
+
handler = logging.StreamHandler(log_stream)
|
28 |
+
logger = logging.getLogger()
|
29 |
+
logger.setLevel(logging.DEBUG) # 원하는 레벨로 설정
|
30 |
+
logger.addHandler(handler)
|
31 |
+
|
32 |
+
def get_logs():
|
33 |
+
"""StringIO에 쌓인 로그를 문자열로 반환"""
|
34 |
+
return log_stream.getvalue()
|
35 |
+
|
36 |
|
37 |
# ------------------------
|
38 |
# 1) DEMO_LIST 및 SystemPrompt
|
39 |
# ------------------------
|
40 |
|
41 |
+
|
42 |
DEMO_LIST = [
|
43 |
{"description": "블록이 위에서 떨어지는 클래식 테트리스 게임을 개발해주세요. 화살표 키로 조작하며, 가로줄이 채워지면 해당 줄이 제거되고 점수가 올라가는 메커니즘이 필요합니다. 난이도는 시간이 지날수록 블록이 빨라지도록 구현하고, 게임오버 조건과 점수 표시 기능을 포함해주세요."},
|
44 |
{"description": "두 명이 번갈아가며 플레이할 수 있는 체스 게임을 만들어주세요. 기본적인 체스 규칙(킹, 퀸, 룩, 비숍, 나이트, 폰의 이동 규칙)을 구현하고, 체크와 체크메이트 감지 기능이 필요합니다. 드래그 앤 드롭으로 말을 움직일 수 있게 하며, 이동 기록도 표시해주세요."},
|
|
|
76 |
{"description": "탑다운 뷰의 간단한 액션 RPG 게임을 개발해주세요. WASD로 이동하고, 마우스 클릭으로 기본 공격, 1-4 키로 특수 스킬을 사용합니다. 플레이어는 몬스터를 처치하며 경험치와 아이템을 획득하고, 레벨업 시 능력치(공격력, 체력, 속도 등)를 향상시킵니다. 다양한 무기와 방어구를 착용할 수 있으며, 스킬 트리 시스템으로 캐릭터를 특화시킬 수 있습니다. 여러 지역과 보스 몬스터, 간단한 퀘스트 시스템도 구현해주세요."},
|
77 |
]
|
78 |
|
79 |
+
|
80 |
SystemPrompt = """
|
81 |
# GameCraft 시스템 프롬프트
|
82 |
|
|
|
218 |
|
219 |
async def try_claude_api(system_message, claude_messages, timeout=15):
|
220 |
"""
|
221 |
+
Claude API 호출 (스트리밍)
|
222 |
"""
|
223 |
try:
|
224 |
+
system_message_with_limit = system_message + "\n\n추가 중요 지침: 생성하는 코드는 절대로 200줄을 넘지 마세요. 코드 간결성이 최우선입니다. 주석을 최소화하고, 핵심 기능만 구현하세요."
|
|
|
225 |
|
226 |
start_time = time.time()
|
227 |
with claude_client.messages.stream(
|
|
|
229 |
max_tokens=19800,
|
230 |
system=system_message_with_limit,
|
231 |
messages=claude_messages,
|
232 |
+
temperature=0.3,
|
233 |
) as stream:
|
234 |
collected_content = ""
|
235 |
for chunk in stream:
|
|
|
249 |
OpenAI API 호출 (스트리밍) - 코드 길이 제한 강화
|
250 |
"""
|
251 |
try:
|
|
|
252 |
if openai_messages and openai_messages[0]["role"] == "system":
|
253 |
+
openai_messages[0]["content"] += "\n\n추가 중요 지침: 생성하는 코드는 절대로 200줄을 넘지 마세요. 코드 간결성이 최우선입니다. 주석은 최소화하고, 핵심 기능만 구현하세요."
|
254 |
+
|
255 |
stream = openai_client.chat.completions.create(
|
256 |
model="o3",
|
257 |
messages=openai_messages,
|
258 |
stream=True,
|
259 |
max_tokens=19800,
|
260 |
+
temperature=0.2
|
261 |
)
|
262 |
collected_content = ""
|
263 |
for chunk in stream:
|
|
|
266 |
yield collected_content
|
267 |
except Exception as e:
|
268 |
raise e
|
269 |
+
|
270 |
|
271 |
# ------------------------
|
272 |
# 4) 템플릿(하나로 통합)
|
273 |
# ------------------------
|
274 |
|
275 |
def load_json_data():
|
|
|
276 |
data_list = []
|
277 |
for item in DEMO_LIST:
|
278 |
data_list.append({
|
|
|
355 |
return gr.HTML(value=html_content)
|
356 |
|
357 |
def load_all_templates():
|
|
|
|
|
|
|
358 |
return create_template_html("🎮 모든 게임 템플릿", load_json_data())
|
359 |
|
360 |
|
|
|
363 |
# ------------------------
|
364 |
|
365 |
def remove_code_block(text):
|
|
|
|
|
|
|
|
|
366 |
pattern = r'```html\s*([\s\S]+?)\s*```'
|
367 |
match = re.search(pattern, text, re.DOTALL)
|
368 |
if match:
|
|
|
378 |
return text.strip()
|
379 |
|
380 |
def optimize_code(code: str) -> str:
|
|
|
|
|
|
|
|
|
381 |
if not code or len(code.strip()) == 0:
|
382 |
return code
|
383 |
|
|
|
411 |
return cleaned_code
|
412 |
|
413 |
def send_to_sandbox(code):
|
|
|
|
|
|
|
414 |
clean_code = remove_code_block(code)
|
415 |
clean_code = optimize_code(clean_code)
|
416 |
|
|
|
455 |
- 최소한의 필수 게임 요소만 포함
|
456 |
"""
|
457 |
try:
|
|
|
458 |
try:
|
459 |
response = claude_client.messages.create(
|
460 |
model="claude-3-7-sonnet-20250219",
|
|
|
643 |
return []
|
644 |
|
645 |
|
646 |
+
####################################################
|
647 |
+
# 1) deploy_to_vercel 함수
|
648 |
+
####################################################
|
649 |
+
def deploy_to_vercel(code: str):
|
650 |
+
print(f"[DEBUG] deploy_to_vercel() 시작. code 길이: {len(code) if code else 0}")
|
651 |
+
try:
|
652 |
+
if not code or len(code.strip()) < 10:
|
653 |
+
print("[DEBUG] 배포 불가: code가 짧음")
|
654 |
+
return "No code to deploy."
|
655 |
+
|
656 |
+
token = "A8IFZmgW2cqA4yUNlLPnci0N"
|
657 |
+
if not token:
|
658 |
+
print("[DEBUG] Vercel 토큰이 없음.")
|
659 |
+
return "Vercel token is not set."
|
660 |
+
|
661 |
+
project_name = ''.join(random.choice(string.ascii_lowercase) for _ in range(6))
|
662 |
+
print(f"[DEBUG] 생성된 project_name: {project_name}")
|
663 |
+
|
664 |
+
deploy_url = "https://api.vercel.com/v13/deployments"
|
665 |
+
headers = {
|
666 |
+
"Authorization": f"Bearer {token}",
|
667 |
+
"Content-Type": "application/json"
|
668 |
+
}
|
669 |
+
|
670 |
+
package_json = {
|
671 |
+
"name": project_name,
|
672 |
+
"version": "1.0.0",
|
673 |
+
"private": True,
|
674 |
+
"dependencies": {"vite": "^5.0.0"},
|
675 |
+
"scripts": {
|
676 |
+
"dev": "vite",
|
677 |
+
"build": "echo 'No build needed' && mkdir -p dist && cp index.html dist/",
|
678 |
+
"preview": "vite preview"
|
679 |
+
}
|
680 |
+
}
|
681 |
+
|
682 |
+
files = [
|
683 |
+
{"file": "index.html", "data": code},
|
684 |
+
{"file": "package.json", "data": json.dumps(package_json, indent=2)}
|
685 |
+
]
|
686 |
+
project_settings = {
|
687 |
+
"buildCommand": "npm run build",
|
688 |
+
"outputDirectory": "dist",
|
689 |
+
"installCommand": "npm install",
|
690 |
+
"framework": None
|
691 |
+
}
|
692 |
+
|
693 |
+
deploy_data = {
|
694 |
+
"name": project_name,
|
695 |
+
"files": files,
|
696 |
+
"target": "production",
|
697 |
+
"projectSettings": project_settings
|
698 |
+
}
|
699 |
+
|
700 |
+
print("[DEBUG] Vercel API 요청 전송중...")
|
701 |
+
deploy_response = requests.post(deploy_url, headers=headers, json=deploy_data)
|
702 |
+
print("[DEBUG] 응답 status_code:", deploy_response.status_code)
|
703 |
+
|
704 |
+
if deploy_response.status_code != 200:
|
705 |
+
print("[DEBUG] 배포 실패:", deploy_response.text)
|
706 |
+
return f"Deployment failed: {deploy_response.text}"
|
707 |
+
|
708 |
+
deployment_url = f"https://{project_name}.vercel.app"
|
709 |
+
print(f"[DEBUG] 배포 성공 -> URL: {deployment_url}")
|
710 |
+
time.sleep(5)
|
711 |
+
|
712 |
+
|
713 |
+
# Markdown 링크로 반환
|
714 |
+
return f"""
|
715 |
+
✅ **Deployment complete!**
|
716 |
+
Your app is live at:
|
717 |
+
[**{deployment_url}**]({deployment_url})
|
718 |
+
"""
|
719 |
+
|
720 |
+
|
721 |
+
|
722 |
+
except Exception as e:
|
723 |
+
print("[ERROR] deploy_to_vercel() 예외:", e)
|
724 |
+
return f"Error during deployment: {str(e)}"
|
725 |
+
|
726 |
+
|
727 |
+
|
728 |
# ------------------------
|
729 |
+
# (3) handle_deploy_legacy
|
730 |
# ------------------------
|
731 |
+
def handle_deploy_legacy(code):
|
732 |
+
logger.debug(f"[handle_deploy_legacy] code 길이: {len(code) if code else 0}")
|
733 |
+
if not code or len(code.strip()) < 10:
|
734 |
+
logger.info("[handle_deploy_legacy] 코드가 짧음.")
|
735 |
+
return "<div style='color:red;'>배포할 코드가 없습니다.</div>"
|
736 |
|
737 |
+
clean_code = remove_code_block(code)
|
738 |
+
logger.debug(f"[handle_deploy_legacy] remove_code_block 후 길이: {len(clean_code)}")
|
739 |
+
|
740 |
+
result_html = deploy_to_vercel(clean_code)
|
741 |
+
logger.debug(f"[handle_deploy_legacy] 배포 결과 HTML 길이: {len(result_html)}")
|
742 |
+
|
743 |
+
encoded_html = base64.b64encode(result_html.encode('utf-8')).decode()
|
744 |
+
data_uri = f"data:text/html;charset=utf-8;base64,{encoded_html}"
|
745 |
+
|
746 |
+
iframe_html = f"""
|
747 |
+
<iframe src="{data_uri}"
|
748 |
+
style="width:100%; height:600px; border:none;"
|
749 |
+
sandbox="allow-scripts allow-same-origin allow-popups">
|
750 |
+
</iframe>
|
751 |
+
"""
|
752 |
+
logger.debug("[handle_deploy_legacy] iframe_html 반환")
|
753 |
+
return iframe_html
|
754 |
+
|
755 |
+
|
756 |
+
# ------------------------
|
757 |
+
# 8) Gradio / Modelscope UI 빌드
|
758 |
+
# ------------------------
|
759 |
demo_instance = Demo()
|
760 |
theme = gr.themes.Soft(
|
761 |
primary_hue="blue",
|
|
|
767 |
)
|
768 |
|
769 |
with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
|
|
|
770 |
header_html = gr.HTML("""
|
771 |
<div class="app-header">
|
772 |
<h1>🎮 Vibe Game Craft</h1>
|
773 |
+
<p>설명을 입력하면 웹 기반 HTML5, JavaScript, CSS 게임을 생성합니다. 실시간 미리보기와 배포 기능도 지원됩니다.</p>
|
774 |
</div>
|
|
|
775 |
<!-- 배포 결과 박스 - 헤더 바로 아래 위치 -->
|
776 |
<div id="deploy-banner" style="display:none;" class="deploy-banner">
|
777 |
+
<!-- (생략) ... 배너 스타일/스크립트 ... -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
778 |
</div>
|
|
|
779 |
<style>
|
780 |
+
/* (생략) ... CSS ... */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
781 |
</style>
|
|
|
782 |
<script>
|
783 |
+
/* (생략) ... JS copyBannerUrl / showDeployBanner ... */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
784 |
</script>
|
785 |
""")
|
786 |
|
787 |
history = gr.State([])
|
788 |
setting = gr.State({"system": SystemPrompt})
|
789 |
+
deploy_status = gr.State({"is_deployed": False,"status": "","url": "","message": ""})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
790 |
|
791 |
with ms.Application() as app:
|
792 |
with antd.ConfigProvider():
|
793 |
|
|
|
794 |
with antd.Drawer(open=False, title="코드 보기", placement="left", width="750px") as code_drawer:
|
795 |
code_output = legacy.Markdown()
|
796 |
|
|
|
797 |
with antd.Drawer(open=False, title="히스토리", placement="left", width="900px") as history_drawer:
|
798 |
+
history_output = legacy.Chatbot(show_label=False, flushing=False, height=960, elem_classes="history_chatbot")
|
|
|
|
|
799 |
|
|
|
800 |
with antd.Drawer(
|
801 |
open=False,
|
802 |
title="게임 템플릿",
|
|
|
809 |
session_history = gr.HTML(elem_classes="session-history")
|
810 |
close_btn = antd.Button("닫기", type="default", elem_classes="close-btn")
|
811 |
|
|
|
812 |
with antd.Row(gutter=[32, 12], align="top", elem_classes="equal-height-container") as layout:
|
813 |
|
814 |
# 왼쪽 Col
|
|
|
816 |
with ms.Div(elem_classes="right_panel panel"):
|
817 |
gr.HTML(r"""
|
818 |
<div class="render_header">
|
819 |
+
<span class="header_btn"></span><span class="header_btn"></span><span class="header_btn"></span>
|
|
|
|
|
820 |
</div>
|
821 |
""")
|
822 |
with antd.Tabs(active_key="empty", render_tab_bar="() => null") as state_tab:
|
823 |
with antd.Tabs.Item(key="empty"):
|
824 |
empty = antd.Empty(description="게임을 만들려면 설명을 입력하세요", elem_classes="right_content")
|
825 |
with antd.Tabs.Item(key="loading"):
|
826 |
+
loading = antd.Spin(True, tip="게임 코드 생성 중...", size="large", elem_classes="right_content")
|
|
|
|
|
827 |
with antd.Tabs.Item(key="render"):
|
828 |
sandbox = gr.HTML(elem_classes="html_content")
|
829 |
|
830 |
# 오른쪽 Col
|
831 |
with antd.Col(span=24, md=8, elem_classes="equal-height-col"):
|
832 |
with antd.Flex(vertical=True, gap="small", elem_classes="right-top-buttons"):
|
|
|
833 |
with antd.Flex(gap="small", elem_classes="setting-buttons", justify="space-between"):
|
834 |
codeBtn = antd.Button("🧑💻 코드 보기", type="default", elem_classes="code-btn")
|
835 |
historyBtn = antd.Button("📜 히스토리", type="default", elem_classes="history-btn")
|
836 |
template_btn = antd.Button("🎮 템플릿", type="default", elem_classes="template-btn")
|
837 |
|
|
|
838 |
with antd.Flex(gap="small", justify="space-between", elem_classes="action-buttons"):
|
839 |
btn = antd.Button("전송", type="primary", size="large", elem_classes="send-btn")
|
840 |
boost_btn = antd.Button("증강", type="default", size="large", elem_classes="boost-btn")
|
|
|
851 |
)
|
852 |
gr.HTML('<div class="help-text">💡 원하는 게임의 설명을 입력하세요. 예: "테트리스 게임 제작해줘."</div>')
|
853 |
|
854 |
+
# Markdown으로 배포 결과 표시
|
855 |
+
deploy_result_container = gr.Markdown(
|
856 |
+
value="아직 배포된 게임이 없습니다.",
|
857 |
+
label="Deployment Result"
|
858 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
859 |
|
860 |
+
# Code Drawer 열기/닫기
|
861 |
+
codeBtn.click(lambda: gr.update(open=True), inputs=[], outputs=[code_drawer])
|
862 |
+
code_drawer.close(lambda: gr.update(open=False), inputs=[], outputs=[code_drawer])
|
863 |
+
|
864 |
+
# History Drawer 열기/닫기
|
865 |
+
historyBtn.click(history_render, inputs=[history], outputs=[history_drawer, history_output])
|
866 |
+
history_drawer.close(lambda: gr.update(open=False), inputs=[], outputs=[history_drawer])
|
|
|
|
|
|
|
|
|
867 |
|
868 |
# Template Drawer
|
869 |
template_btn.click(
|
|
|
871 |
outputs=[session_drawer, session_history],
|
872 |
queue=False
|
873 |
)
|
874 |
+
session_drawer.close(lambda: (gr.update(open=False), gr.HTML("")), outputs=[session_drawer, session_history])
|
875 |
+
close_btn.click(lambda: (gr.update(open=False), gr.HTML("")), outputs=[session_drawer, session_history])
|
|
|
|
|
|
|
|
|
|
|
|
|
876 |
|
877 |
# 전송 버튼
|
878 |
btn.click(
|
|
|
882 |
)
|
883 |
|
884 |
# 클리어 버튼
|
885 |
+
clear_btn.click(demo_instance.clear_history, inputs=[], outputs=[history])
|
|
|
|
|
|
|
|
|
886 |
|
887 |
# 증강 버튼
|
888 |
boost_btn.click(
|
|
|
898 |
outputs=[sandbox, state_tab]
|
899 |
)
|
900 |
|
901 |
+
# 배포 버튼 → deploy_result_container (Markdown)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
902 |
deploy_btn.click(
|
903 |
+
fn=lambda code: deploy_to_vercel(remove_code_block(code)) if code else "No code generated.",
|
904 |
+
inputs=[code_output],
|
905 |
+
outputs=[deploy_result_container]
|
906 |
)
|
907 |
|
|
|
908 |
# ------------------------
|
909 |
+
# 9) 실행
|
910 |
# ------------------------
|
|
|
911 |
if __name__ == "__main__":
|
912 |
try:
|
913 |
demo_instance = Demo()
|