openfree commited on
Commit
6fb9a50
·
verified ·
1 Parent(s): e0aa73b

Update app-backup.py

Browse files
Files changed (1) hide show
  1. 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줄을 넘지 마세요. 코드 간결성이 최우선입니다. 주석을 최소화하고, 핵심 기능만 구현하세요. 불필요한 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,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
- # 7) Gradio / Modelscope UI 빌드
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 게임을 생성합니다. 직관적인 인터페이스로 쉽게 게임을 만들고, 실시간으로 미리보기를 확인하세요.</p>
669
  </div>
670
-
671
  <!-- 배포 결과 박스 - 헤더 바로 아래 위치 -->
672
  <div id="deploy-banner" style="display:none;" class="deploy-banner">
673
- <div class="deploy-banner-content">
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
- // URL 복사 함수
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
- deploy_result_container = gr.HTML(
941
- """
942
- <div class="deploy-section">
943
- <div class="deploy-header">📤 배포 결과</div>
944
- <div id="deploy-result-box" class="deploy-result-box">
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
- # History Drawer
968
- historyBtn.click(
969
- history_render,
970
- inputs=[history],
971
- outputs=[history_drawer, history_output]
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
- lambda: (gr.update(open=False), gr.HTML("")),
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=handle_deploy,
1181
- inputs=[code_output, deploy_status],
1182
- outputs=[deploy_result_container, deploy_status]
1183
  )
1184
 
1185
-
1186
  # ------------------------
1187
- # 8) 실제 실행
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()