openfree commited on
Commit
46e81e0
·
verified ·
1 Parent(s): b609ab4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +206 -218
app.py CHANGED
@@ -646,8 +646,200 @@ class Demo:
646
  return []
647
 
648
 
 
649
  # ------------------------
650
- # 7) Gradio / Modelscope UI 빌드
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651
  # ------------------------
652
 
653
  demo_instance = Demo()
@@ -665,7 +857,7 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
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
  <!-- 배포 결과 박스 - 헤더 바로 아래 위치 -->
@@ -684,7 +876,6 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
684
  </div>
685
 
686
  <style>
687
- /* 전체 앱 스타일 */
688
  :root {
689
  --primary-color: #9c89b8;
690
  --secondary-color: #f0a6ca;
@@ -696,14 +887,11 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
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;
@@ -713,22 +901,18 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
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);
@@ -739,44 +923,35 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
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;
@@ -786,7 +961,6 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
786
  display: flex;
787
  align-items: center;
788
  }
789
-
790
  .deploy-banner-url {
791
  color: #0066cc;
792
  text-decoration: none;
@@ -794,7 +968,6 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
794
  word-break: break-all;
795
  flex: 1;
796
  }
797
-
798
  .deploy-banner-copy-btn {
799
  background: #0066cc;
800
  color: white;
@@ -805,14 +978,12 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
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)
@@ -825,8 +996,6 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
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');
@@ -871,9 +1040,7 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
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(
@@ -936,17 +1103,18 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
936
  max_length=100000
937
  )
938
  gr.HTML('<div class="help-text">💡 원하는 게임의 설명을 입력하세요. 예: "테트리스 게임 제작해줘."</div>')
 
 
939
  deploy_result_container = gr.HTML(
940
  """
941
  <div class="deploy-section">
942
  <div class="deploy-header">📤 배포 결과</div>
943
  <div id="deploy-result-box" class="deploy-result-box">
944
- <div class="no-deploy">아직 배포된 게임이 없습니다.</div>
945
  </div>
946
  </div>
947
  """
948
  )
949
-
950
 
951
  js_trigger = gr.HTML(elem_id="js-trigger", visible=False)
952
 
@@ -1018,195 +1186,15 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
1018
  outputs=[sandbox, state_tab]
1019
  )
1020
 
1021
- def deploy_to_vercel(code: str):
1022
- try:
1023
- if not code or len(code.strip()) < 10:
1024
- return "<div style='color:red; padding:10px; border:1px solid red; border-radius:5px;'>배포할 코드가 없습니다. 먼저 게임 코드를 생성해주세요.</div>"
1025
-
1026
- token = "A8IFZmgW2cqA4yUNlLPnci0N"
1027
- if not token:
1028
- return "<div style='color:red; padding:10px; border:1px solid red; border-radius:5px;'>Vercel 토큰이 설정되지 않았습니다.</div>"
1029
-
1030
- # 프로젝트 이름 생성
1031
- project_name = ''.join(random.choice(string.ascii_lowercase) for i in range(6))
1032
-
1033
- # Vercel API 요청 준비
1034
- deploy_url = "https://api.vercel.com/v13/deployments"
1035
- headers = {
1036
- "Authorization": f"Bearer {token}",
1037
- "Content-Type": "application/json"
1038
- }
1039
-
1040
- # 패키지 정보
1041
- package_json = {
1042
- "name": project_name,
1043
- "version": "1.0.0",
1044
- "private": True,
1045
- "dependencies": {"vite": "^5.0.0"},
1046
- "scripts": {
1047
- "dev": "vite",
1048
- "build": "echo 'No build needed' && mkdir -p dist && cp index.html dist/",
1049
- "preview": "vite preview"
1050
- }
1051
- }
1052
-
1053
- # 배포할 파일들
1054
- files = [
1055
- {"file": "index.html", "data": code},
1056
- {"file": "package.json", "data": json.dumps(package_json, indent=2)}
1057
- ]
1058
-
1059
- # 프로젝트 설정
1060
- project_settings = {
1061
- "buildCommand": "npm run build",
1062
- "outputDirectory": "dist",
1063
- "installCommand": "npm install",
1064
- "framework": None
1065
- }
1066
-
1067
- # 배포 데이터
1068
- deploy_data = {
1069
- "name": project_name,
1070
- "files": files,
1071
- "target": "production",
1072
- "projectSettings": project_settings
1073
- }
1074
-
1075
- # API 요청 및 응답 처리
1076
- deploy_response = requests.post(deploy_url, headers=headers, json=deploy_data)
1077
- if deploy_response.status_code != 200:
1078
- return f"<div style='color:red; padding:10px; border:1px solid red; border-radius:5px;'>배포 실패: {deploy_response.text}</div>"
1079
-
1080
- # 배포 URL 생성
1081
- deployment_url = f"https://{project_name}.vercel.app"
1082
-
1083
- # 배포 완료 기다리기
1084
- time.sleep(3)
1085
-
1086
- # 결과 반환
1087
- return f"""
1088
- <div style='padding: 15px; background-color: #f0fff4; border: 1px solid #34c759; border-radius: 8px;'>
1089
- <h3 style='margin-top: 0; color: #34c759;'>✅ 배포 완료!</h3>
1090
- <p>게임이 성공적으로 배포되었습니다.</p>
1091
- <div style='margin-top: 10px; padding: 10px; background: #f8fafc; border-radius: 6px; display: flex; justify-content: space-between; align-items: center;'>
1092
- <a href="{deployment_url}" target="_blank" style='color: #0066cc; text-decoration: none; word-break: break-all;'>{deployment_url}</a>
1093
- <button onclick="navigator.clipboard.writeText('{deployment_url}')" style='background: #0066cc; color: white; border: none; border-radius: 4px; padding: 5px 10px; cursor: pointer;'>복사</button>
1094
- </div>
1095
- </div>
1096
- """
1097
- except Exception as e:
1098
- return f"<div style='color:red; padding:10px; border:1px solid red; border-radius:5px;'>오류 발생: {str(e)}</div>"
1099
-
1100
-
1101
- # handle_deploy
1102
- def handle_deploy(code, deploy_status):
1103
- if not code:
1104
- js_code = """
1105
- <script>
1106
- showDeployBanner('error', '⚠️ 배포 실패', '배포할 코드가 없습니다. 먼저 게임 코드를 생성해주세요.');
1107
- document.getElementById('deploy-result-box').innerHTML = `
1108
- <div class="deploy-error">
1109
- <div class="error-icon">⚠️</div>
1110
- <div class="error-message">배포할 코드가 없습니다.</div>
1111
- </div>
1112
- `;
1113
- </script>
1114
- """
1115
- return js_code, {
1116
- "is_deployed": False,
1117
- "status": "error",
1118
- "message": "배포할 코드가 없습니다.",
1119
- "url": ""
1120
- }
1121
-
1122
- try:
1123
- loading_js = """
1124
- <script>
1125
- showDeployBanner('loading', '🔄 배포 진행 중', 'Vercel에 게임을 배포하고 있습니다. 잠시만 기다려주세요.');
1126
- document.getElementById('deploy-result-box').innerHTML = `
1127
- <div class="deploy-loading">
1128
- <div class="loading-spinner"></div>
1129
- <div class="loading-message">Vercel에 배포 중입니다...</div>
1130
- </div>
1131
- `;
1132
- </script>
1133
- """
1134
- yield loading_js, deploy_status
1135
-
1136
- clean_code = remove_code_block(code)
1137
- result = deploy_to_vercel(clean_code)
1138
-
1139
- if result.get("status") == "success":
1140
- url = result.get("url")
1141
- success_js = f"""
1142
- <script>
1143
- showDeployBanner('success', '✅ 배포 완료!', '게임이 성공적으로 Vercel에 배포되었습니다.', '{url}');
1144
- document.getElementById('deploy-result-box').innerHTML = `
1145
- <div class="deploy-success">
1146
- <div class="success-icon">✅</div>
1147
- <div class="success-message">배포 완료!</div>
1148
- <div class="url-box">
1149
- <a href="{url}" target="_blank">{url}</a>
1150
- <button class="copy-btn" onclick="navigator.clipboard.writeText('{url}')">복사</button>
1151
- </div>
1152
- </div>
1153
- `;
1154
- </script>
1155
- """
1156
- return success_js, {
1157
- "is_deployed": True,
1158
- "status": "success",
1159
- "url": url,
1160
- "message": "배포 완료!"
1161
- }
1162
- else:
1163
- error_msg = result.get("message", "알 수 없는 오류")
1164
- error_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 error_js, {
1176
- "is_deployed": False,
1177
- "status": "error",
1178
- "message": error_msg,
1179
- "url": ""
1180
- }
1181
-
1182
- except Exception as e:
1183
- error_msg = str(e)
1184
- exception_js = f"""
1185
- <script>
1186
- showDeployBanner('error', '⚠️ 시스템 오류', '{error_msg}');
1187
- document.getElementById('deploy-result-box').innerHTML = `
1188
- <div class="deploy-error">
1189
- <div class="error-icon">⚠️</div>
1190
- <div class="error-message">시스템 오류: {error_msg}</div>
1191
- </div>
1192
- `;
1193
- </script>
1194
- """
1195
- return exception_js, {
1196
- "is_deployed": False,
1197
- "status": "error",
1198
- "message": error_msg,
1199
- "url": ""
1200
- }
1201
-
1202
- # deploy_btn.click 함수를 찾아 다음으로 완전히 교체하세요
1203
  deploy_btn.click(
1204
- fn=lambda code: deploy_to_vercel(remove_code_block(code)),
1205
- inputs=[code_output],
1206
- outputs=[deploy_result_container]
1207
- )
 
1208
  # ------------------------
1209
- # 8) 실제 실행
1210
  # ------------------------
1211
 
1212
  if __name__ == "__main__":
@@ -1215,4 +1203,4 @@ if __name__ == "__main__":
1215
  demo.queue(default_concurrency_limit=20).launch(ssr_mode=False)
1216
  except Exception as e:
1217
  print(f"Initialization error: {e}")
1218
- raise
 
646
  return []
647
 
648
 
649
+
650
  # ------------------------
651
+ # 7) 실제 배포 함수를 수정하여 화면 표시
652
+ # ------------------------
653
+
654
+ def deploy_to_vercel(code: str):
655
+ try:
656
+ if not code or len(code.strip()) < 10:
657
+ # 반환할 HTML: "배포할 코드가 없습니다."
658
+ return """
659
+ <div style='color:red; padding:10px; border:1px solid red; border-radius:5px;'>
660
+ 배포할 코드가 없습니다. 먼저 게임 코드를 생성해주세요.
661
+ </div>
662
+ """
663
+
664
+ token = "A8IFZmgW2cqA4yUNlLPnci0N"
665
+ if not token:
666
+ return """
667
+ <div style='color:red; padding:10px; border:1px solid red; border-radius:5px;'>
668
+ Vercel 토큰이 설정되지 않았습니다.
669
+ </div>
670
+ """
671
+
672
+ project_name = ''.join(random.choice(string.ascii_lowercase) for i in range(6))
673
+ deploy_url = "https://api.vercel.com/v13/deployments"
674
+ headers = {
675
+ "Authorization": f"Bearer {token}",
676
+ "Content-Type": "application/json"
677
+ }
678
+ package_json = {
679
+ "name": project_name,
680
+ "version": "1.0.0",
681
+ "private": True,
682
+ "dependencies": {"vite": "^5.0.0"},
683
+ "scripts": {
684
+ "dev": "vite",
685
+ "build": "echo 'No build needed' && mkdir -p dist && cp index.html dist/",
686
+ "preview": "vite preview"
687
+ }
688
+ }
689
+ files = [
690
+ {"file": "index.html", "data": code},
691
+ {"file": "package.json", "data": json.dumps(package_json, indent=2)}
692
+ ]
693
+ project_settings = {
694
+ "buildCommand": "npm run build",
695
+ "outputDirectory": "dist",
696
+ "installCommand": "npm install",
697
+ "framework": None
698
+ }
699
+ deploy_data = {
700
+ "name": project_name,
701
+ "files": files,
702
+ "target": "production",
703
+ "projectSettings": project_settings
704
+ }
705
+
706
+ deploy_response = requests.post(deploy_url, headers=headers, json=deploy_data)
707
+ if deploy_response.status_code != 200:
708
+ return f"""
709
+ <div style='color:red; padding:10px; border:1px solid red; border-radius:5px;'>
710
+ 배포 실패: {html.escape(deploy_response.text)}
711
+ </div>
712
+ """
713
+
714
+ deployment_url = f"https://{project_name}.vercel.app"
715
+ time.sleep(3)
716
+
717
+ return f"""
718
+ <div style='padding: 15px; background-color: #f0fff4; border: 1px solid #34c759; border-radius: 8px;'>
719
+ <h3 style='margin-top: 0; color: #34c759;'>✅ 배포 완료!</h3>
720
+ <p>게임이 성공적으로 배포되었습니다.</p>
721
+ <div style='margin-top: 10px; padding: 10px; background: #f8fafc; border-radius: 6px; display: flex; justify-content: space-between; align-items: center;'>
722
+ <a href="{deployment_url}" target="_blank" style='color: #0066cc; text-decoration: none; word-break: break-all;'>{deployment_url}</a>
723
+ <button onclick="navigator.clipboard.writeText('{deployment_url}')" style='background: #0066cc; color: white; border: none; border-radius: 4px; padding: 5px 10px; cursor: pointer;'>복사</button>
724
+ </div>
725
+ </div>
726
+ <script>
727
+ showDeployBanner('success','✅ 배포 완료!','게임이 성공적으로 배포되었습니다.','{deployment_url}');
728
+ document.getElementById('deploy-result-box').innerHTML = `
729
+ <div class="deploy-success">
730
+ <div class="success-icon">✅</div>
731
+ <div class="success-message">배포 완료!</div>
732
+ <div class="url-box">
733
+ <a href="{deployment_url}" target="_blank">{deployment_url}</a>
734
+ <button class="copy-btn" onclick="navigator.clipboard.writeText('{deployment_url}')">복사</button>
735
+ </div>
736
+ </div>
737
+ `;
738
+ </script>
739
+ """
740
+ except Exception as e:
741
+ return f"""
742
+ <div style='color:red; padding:10px; border:1px solid red; border-radius:5px;'>
743
+ 오류 발생: {html.escape(str(e))}
744
+ </div>
745
+ <script>
746
+ showDeployBanner('error','⚠️ 시스템 오류','{html.escape(str(e))}');
747
+ </script>
748
+ """
749
+
750
+
751
+ # ------------------------
752
+ # "handle_deploy" 수정: 실제로 사용하여 결과 표시
753
+ # ------------------------
754
+ def handle_deploy(code, deploy_status):
755
+ """
756
+ 이 함수를 호출하면:
757
+ 1) 로딩 상태를 먼저 반환 (yield)
758
+ 2) 실제 deploy_to_vercel() 실행 후 성공/실패 결과 HTML 반환
759
+ """
760
+ if not code:
761
+ # 코드가 없는 경우 즉시 에러 메시지
762
+ js_code = """
763
+ <script>
764
+ showDeployBanner('error', '⚠️ 배포 실패', '배포할 코드가 없습니다. 먼저 게임 코드를 생성해주세요.');
765
+ document.getElementById('deploy-result-box').innerHTML = `
766
+ <div class="deploy-error">
767
+ <div class="error-icon">⚠️</div>
768
+ <div class="error-message">배포할 코드가 없습니다.</div>
769
+ </div>
770
+ `;
771
+ </script>
772
+ """
773
+ return js_code, {
774
+ "is_deployed": False,
775
+ "status": "error",
776
+ "message": "배포할 코드가 없습니다.",
777
+ "url": ""
778
+ }
779
+
780
+ try:
781
+ # 1) 로딩 상태 표시
782
+ loading_js = """
783
+ <script>
784
+ showDeployBanner('loading', '🔄 배포 진행 중', 'Vercel에 게임을 배포하고 있습니다. 잠시만 기다려주세요.');
785
+ document.getElementById('deploy-result-box').innerHTML = `
786
+ <div class="deploy-loading">
787
+ <div class="loading-spinner"></div>
788
+ <div class="loading-message">Vercel에 배포 중입니다...</div>
789
+ </div>
790
+ `;
791
+ </script>
792
+ """
793
+ # Gradio에서는 "yield"를 사용하면 스트리밍 가능하지만,
794
+ # 간단히 한 번에 반환해도 됨. 여기서는 예시로 스트리밍 없이 진행.
795
+ # yield loading_js, deploy_status
796
+
797
+ # 2) 실제 배포 실행
798
+ clean_code = remove_code_block(code)
799
+ result_html = deploy_to_vercel(clean_code)
800
+
801
+ # 배포 결과를 화면에 표시
802
+ # 배포 성공 시 'success', 실패 시 해당 메시지
803
+ if "✅ 배포 완료!" in result_html:
804
+ # 성공
805
+ return result_html, {
806
+ "is_deployed": True,
807
+ "status": "success",
808
+ "url": "",
809
+ "message": "배포 성공"
810
+ }
811
+ else:
812
+ # 실패 또는 오류
813
+ return result_html, {
814
+ "is_deployed": False,
815
+ "status": "error",
816
+ "url": "",
817
+ "message": "배포 실패"
818
+ }
819
+
820
+ except Exception as e:
821
+ error_msg = str(e)
822
+ exception_js = f"""
823
+ <script>
824
+ showDeployBanner('error', '⚠️ 시스템 오류', '{error_msg}');
825
+ document.getElementById('deploy-result-box').innerHTML = `
826
+ <div class="deploy-error">
827
+ <div class="error-icon">⚠️</div>
828
+ <div class="error-message">시스템 오류: {error_msg}</div>
829
+ </div>
830
+ `;
831
+ </script>
832
+ """
833
+ return exception_js, {
834
+ "is_deployed": False,
835
+ "status": "error",
836
+ "message": error_msg,
837
+ "url": ""
838
+ }
839
+
840
+
841
+ # ------------------------
842
+ # 8) Gradio / Modelscope UI 빌드
843
  # ------------------------
844
 
845
  demo_instance = Demo()
 
857
  header_html = gr.HTML("""
858
  <div class="app-header">
859
  <h1>🎮 Vibe Game Craft</h1>
860
+ <p>설명을 입력하면 웹 기반 HTML5, JavaScript, CSS 게임을 생성합니다. 실시간 미리보기와 배포 기능도 지원됩니다.</p>
861
  </div>
862
 
863
  <!-- 배포 결과 박스 - 헤더 바로 아래 위치 -->
 
876
  </div>
877
 
878
  <style>
 
879
  :root {
880
  --primary-color: #9c89b8;
881
  --secondary-color: #f0a6ca;
 
887
  --shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
888
  --radius: 12px;
889
  }
 
890
  body {
891
  background-color: var(--background-color);
892
  color: var(--text-color);
893
  font-family: 'Poppins', sans-serif;
894
  }
 
 
895
  .app-header {
896
  text-align: center;
897
  padding: 1.5rem 1rem;
 
901
  box-shadow: var(--shadow);
902
  color: white;
903
  }
 
904
  .app-header h1 {
905
  font-size: 2.5rem;
906
  font-weight: 700;
907
  margin-bottom: 0.5rem;
908
  text-shadow: 0 2px 4px rgba(0,0,0,0.1);
909
  }
 
910
  .app-header p {
911
  font-size: 1.1rem;
912
  opacity: 0.9;
913
  max-width: 800px;
914
  margin: 0 auto;
915
  }
 
 
916
  .deploy-banner {
917
  background: white;
918
  border-radius: var(--radius);
 
923
  overflow: hidden;
924
  transition: all 0.3s ease;
925
  }
 
926
  .deploy-banner.success {
927
  border-left: 5px solid #34c759;
928
  }
 
929
  .deploy-banner.error {
930
  border-left: 5px solid #ff3b30;
931
  }
 
932
  .deploy-banner.loading {
933
  border-left: 5px solid #007aff;
934
  }
 
935
  .deploy-banner-content {
936
  display: flex;
937
  align-items: center;
938
  padding: 15px 20px;
939
  }
 
940
  .deploy-banner-icon {
941
  font-size: 24px;
942
  margin-right: 15px;
943
  }
 
944
  .deploy-banner-info {
945
  flex: 1;
946
  }
 
947
  .deploy-banner-title {
948
  font-weight: bold;
949
  font-size: 16px;
950
  margin-bottom: 5px;
951
  }
 
952
  .deploy-banner-message {
953
  color: #666;
954
  }
 
955
  .deploy-banner-url-container {
956
  background: #f5f8ff;
957
  padding: 10px 15px;
 
961
  display: flex;
962
  align-items: center;
963
  }
 
964
  .deploy-banner-url {
965
  color: #0066cc;
966
  text-decoration: none;
 
968
  word-break: break-all;
969
  flex: 1;
970
  }
 
971
  .deploy-banner-copy-btn {
972
  background: #0066cc;
973
  color: white;
 
978
  cursor: pointer;
979
  font-size: 12px;
980
  }
 
981
  .deploy-banner-copy-btn:hover {
982
  background: #0052a3;
983
  }
984
  </style>
985
 
986
  <script>
 
987
  function copyBannerUrl() {
988
  const url = document.getElementById('deploy-banner-url').href;
989
  navigator.clipboard.writeText(url)
 
996
  }, 1000);
997
  });
998
  }
 
 
999
  function showDeployBanner(type, title, message, url) {
1000
  const banner = document.getElementById('deploy-banner');
1001
  const bannerTitle = document.getElementById('deploy-banner-title');
 
1040
 
1041
  # history Drawer
1042
  with antd.Drawer(open=False, title="히스토리", placement="left", width="900px") as history_drawer:
1043
+ history_output = legacy.Chatbot(show_label=False, flushing=False, height=960, elem_classes="history_chatbot")
 
 
1044
 
1045
  # templates Drawer
1046
  with antd.Drawer(
 
1103
  max_length=100000
1104
  )
1105
  gr.HTML('<div class="help-text">💡 원하는 게임의 설명을 입력하세요. 예: "테트리스 게임 제작해줘."</div>')
1106
+
1107
+ # 배포 결과를 보여줄 HTML 박스
1108
  deploy_result_container = gr.HTML(
1109
  """
1110
  <div class="deploy-section">
1111
  <div class="deploy-header">📤 배포 결과</div>
1112
  <div id="deploy-result-box" class="deploy-result-box">
1113
+ <div class="no-deploy">아직 배포된 게임이 없습니다.</div>
1114
  </div>
1115
  </div>
1116
  """
1117
  )
 
1118
 
1119
  js_trigger = gr.HTML(elem_id="js-trigger", visible=False)
1120
 
 
1186
  outputs=[sandbox, state_tab]
1187
  )
1188
 
1189
+ # **핵심: 배포 버튼 -> handle_deploy 사용**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1190
  deploy_btn.click(
1191
+ fn=handle_deploy,
1192
+ inputs=[code_output, deploy_status], # code, deploy_status
1193
+ outputs=[deploy_result_container, deploy_status]
1194
+ )
1195
+
1196
  # ------------------------
1197
+ # 9) 실행
1198
  # ------------------------
1199
 
1200
  if __name__ == "__main__":
 
1203
  demo.queue(default_concurrency_limit=20).launch(ssr_mode=False)
1204
  except Exception as e:
1205
  print(f"Initialization error: {e}")
1206
+ raise