openfree commited on
Commit
4feca98
·
verified ·
1 Parent(s): 357ce1c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +241 -196
app.py CHANGED
@@ -945,6 +945,10 @@ class Demo:
945
  # 7) Gradio / Modelscope UI 빌드
946
  # ------------------------
947
 
 
 
 
 
948
  demo_instance = Demo()
949
  theme = gr.themes.Soft(
950
  primary_hue="blue",
@@ -956,7 +960,28 @@ theme = gr.themes.Soft(
956
  )
957
 
958
  with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
959
- gr.HTML("""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
960
  <style>
961
  /* 전체 앱 스타일 */
962
  :root {
@@ -981,7 +1006,7 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
981
  .app-header {
982
  text-align: center;
983
  padding: 1.5rem 1rem;
984
- margin-bottom: 1.5rem;
985
  background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
986
  border-radius: var(--radius);
987
  box-shadow: var(--shadow);
@@ -1002,185 +1027,133 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
1002
  margin: 0 auto;
1003
  }
1004
 
1005
- /* 배포 알림 스타일 - 상단에 고정 표시 */
1006
- .deploy-alert {
1007
- position: fixed;
1008
- top: 10px;
1009
- left: 50%;
1010
- transform: translateX(-50%);
1011
- z-index: 9999;
1012
- background: rgba(255, 255, 255, 0.95);
1013
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
1014
- border-radius: 10px;
1015
- padding: 15px 25px;
1016
- min-width: 300px;
1017
- text-align: center;
1018
- animation: slideDown 0.5s ease-out;
1019
  }
1020
 
1021
- .deploy-alert.success {
1022
  border-left: 5px solid #34c759;
1023
  }
1024
 
1025
- .deploy-alert.error {
1026
  border-left: 5px solid #ff3b30;
1027
  }
1028
 
1029
- .deploy-alert h3 {
1030
- margin: 0 0 10px 0;
1031
- font-size: 16px;
1032
- }
1033
-
1034
- .deploy-url {
1035
- background: #f5f8ff;
1036
- padding: 10px;
1037
- border-radius: 6px;
1038
- margin: 10px 0;
1039
- word-break: break-all;
1040
- font-weight: bold;
1041
  }
1042
 
1043
- .deploy-url a {
1044
- color: #0066cc;
1045
- text-decoration: none;
1046
- }
1047
-
1048
- @keyframes slideDown {
1049
- from { transform: translate(-50%, -50px); opacity: 0; }
1050
- to { transform: translate(-50%, 0); opacity: 1; }
1051
- }
1052
-
1053
- /* 하단 고정 배포 상태 바 */
1054
- .deploy-status-bar {
1055
- position: fixed;
1056
- bottom: 0;
1057
- left: 0;
1058
- width: 100%;
1059
- background: rgba(255, 255, 255, 0.95);
1060
- border-top: 1px solid #ddd;
1061
- padding: 10px 20px;
1062
- z-index: 1000;
1063
  display: flex;
1064
  align-items: center;
1065
- justify-content: center;
1066
  }
1067
 
1068
- .deploy-status-content {
1069
- background: #f0f9ff;
1070
- border-radius: 8px;
1071
- padding: 10px 15px;
1072
- display: flex;
1073
- align-items: center;
1074
- max-width: 800px;
1075
- width: 100%;
1076
  }
1077
 
1078
- .deploy-status-content.success {
1079
- background: #f0fff4;
1080
- border-left: 4px solid #34c759;
1081
  }
1082
 
1083
- .deploy-status-content.error {
1084
- background: #fff0f0;
1085
- border-left: 4px solid #ff3b30;
 
1086
  }
1087
 
1088
- .status-icon {
1089
- font-size: 18px;
1090
- margin-right: 10px;
1091
  }
1092
 
1093
- .status-message {
1094
- flex: 1;
 
 
 
 
 
 
1095
  }
1096
 
1097
- .status-url {
1098
- font-weight: bold;
1099
  color: #0066cc;
1100
- margin-left: 15px;
1101
- text-decoration: underline;
1102
  word-break: break-all;
 
1103
  }
1104
- </style>
1105
 
1106
- <div class="app-header">
1107
- <h1>🎮 Vibe Game Craft</h1>
1108
- <p>설명을 입력하면 웹 기반 HTML5, JavaScript, CSS 게임을 생성합니다. 직관적인 인터페이스로 쉽게 게임을 만들고, 실시간으로 미리보기를 확인하세요.</p>
1109
- </div>
1110
-
1111
- <!-- 배포 알림 - 기본적으로 숨겨져 있음 -->
1112
- <div id="deploy-alert" style="display:none;" class="deploy-alert">
1113
- <h3 id="deploy-alert-title">배포 상태</h3>
1114
- <div id="deploy-alert-content"></div>
1115
- </div>
1116
 
1117
- <!-- 하단 고정 배포 상태 바 - 기본적으로 숨겨져 있음 -->
1118
- <div id="deploy-status-bar" style="display:none;" class="deploy-status-bar">
1119
- <div id="deploy-status-content" class="deploy-status-content">
1120
- <span id="status-icon" class="status-icon">🔄</span>
1121
- <span id="status-message" class="status-message">배포 중...</span>
1122
- <a id="status-url" class="status-url" href="#" target="_blank" style="display:none;"></a>
1123
- </div>
1124
- </div>
1125
 
1126
  <script>
1127
- // 배포 알림 표시 함수
1128
- function showDeployAlert(type, title, content, url) {
1129
- const alertEl = document.getElementById('deploy-alert');
1130
- const titleEl = document.getElementById('deploy-alert-title');
1131
- const contentEl = document.getElementById('deploy-alert-content');
1132
-
1133
- // 타입에 따른 스타일 설정
1134
- alertEl.className = 'deploy-alert ' + type;
1135
- titleEl.textContent = title;
1136
-
1137
- // URL이 있으면 링크 포함
1138
- if (url) {
1139
- contentEl.innerHTML = `
1140
- <p>${content}</p>
1141
- <div class="deploy-url">
1142
- <a href="${url}" target="_blank">${url}</a>
1143
- </div>
1144
- `;
1145
- } else {
1146
- contentEl.innerHTML = `<p>${content}</p>`;
1147
- }
1148
-
1149
- // 알림 표시
1150
- alertEl.style.display = 'block';
1151
-
1152
- // 5초 후 자동으로 숨김
1153
- setTimeout(() => {
1154
- alertEl.style.display = 'none';
1155
- }, 8000);
1156
  }
1157
 
1158
- // 배포 상태 업데이트 함수
1159
- function updateDeployStatus(type, message, url) {
1160
- const statusBar = document.getElementById('deploy-status-bar');
1161
- const statusContent = document.getElementById('deploy-status-content');
1162
- const statusIcon = document.getElementById('status-icon');
1163
- const statusMessage = document.getElementById('status-message');
1164
- const statusUrl = document.getElementById('status-url');
 
 
 
1165
 
1166
- // 상태 타입에 따른 설정
1167
- statusContent.className = 'deploy-status-content ' + type;
 
1168
 
1169
- if (type === 'success') {
1170
- statusIcon.textContent = '✅';
1171
- statusUrl.href = url;
1172
- statusUrl.textContent = url;
1173
- statusUrl.style.display = 'inline';
1174
- } else if (type === 'error') {
1175
- statusIcon.textContent = '❌';
1176
- statusUrl.style.display = 'none';
1177
  } else {
1178
- statusIcon.textContent = '🔄';
1179
- statusUrl.style.display = 'none';
1180
  }
1181
 
1182
- statusMessage.textContent = message;
1183
- statusBar.style.display = 'flex';
1184
  }
1185
  </script>
1186
  """)
@@ -1274,8 +1247,8 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
1274
  )
1275
  gr.HTML('<div class="help-text">💡 원하는 게임의 설명을 입력하세요. 예: "테트리스 게임 제작해줘."</div>')
1276
 
1277
- # ── (4) 배포 결과 영역 - 이제 JavaScript로 동적 업데이트 ──
1278
- deploy_result_html = gr.HTML("""
1279
  <div class="deploy-section">
1280
  <div class="deploy-header">📤 배포 결과</div>
1281
  <div id="deploy-result-box" class="deploy-result-box">
@@ -1357,14 +1330,82 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
1357
  outputs=[sandbox, state_tab]
1358
  )
1359
 
1360
- # (H) '배포' 버튼 => Vercel
1361
- # 수정된 배포 처리 함수
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1362
  def handle_deploy(code, deploy_status):
1363
  if not code:
1364
- # JavaScript 실행 코드: 상태 업데이트
1365
  js_code = """
1366
  <script>
1367
- showDeployAlert('error', '⚠️ 배포 실패', '배포할 코드가 없습니다. 먼저 게임 코드를 생성해주세요.');
 
 
 
1368
  document.getElementById('deploy-result-box').innerHTML = `
1369
  <div class="deploy-error">
1370
  <div class="error-icon">⚠️</div>
@@ -1373,7 +1414,6 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
1373
  `;
1374
  </script>
1375
  """
1376
-
1377
  return js_code, {
1378
  "is_deployed": False,
1379
  "status": "error",
@@ -1382,10 +1422,13 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
1382
  }
1383
 
1384
  try:
1385
- # 배포 시작을 알리는 JavaScript
1386
- yield """
1387
  <script>
1388
- updateDeployStatus('loading', '게임을 Vercel에 배포 중입니다...', '');
 
 
 
1389
  document.getElementById('deploy-result-box').innerHTML = `
1390
  <div class="deploy-loading">
1391
  <div class="loading-spinner"></div>
@@ -1393,23 +1436,26 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
1393
  </div>
1394
  `;
1395
  </script>
1396
- """, deploy_status
 
 
1397
 
1398
  # 코드 전처리
1399
  clean_code = remove_code_block(code)
1400
 
1401
- # Vercel에 배포
1402
- project_name = ''.join(random.choice(string.ascii_lowercase) for i in range(6))
1403
  result = deploy_to_vercel(clean_code)
1404
 
1405
- # 성공적으로 배포된 경우
1406
- if isinstance(result, dict) and result.get("status") == "success":
1407
  url = result.get("url")
1408
- # JavaScript 실행 코드: 배포 성공 표시
1409
- js_code = f"""
1410
  <script>
1411
- showDeployAlert('success', '✅ 배포 완료!', '게임이 성공적으로 배포되었습니다.', '{url}');
1412
- updateDeployStatus('success', '배포 완료!', '{url}');
 
 
1413
  document.getElementById('deploy-result-box').innerHTML = `
1414
  <div class="deploy-success">
1415
  <div class="success-icon">✅</div>
@@ -1422,47 +1468,47 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
1422
  `;
1423
  </script>
1424
  """
1425
-
1426
- return js_code, {
1427
  "is_deployed": True,
1428
  "status": "success",
1429
  "url": url,
1430
  "message": "배포 완료!"
1431
  }
1432
 
1433
- # 실패한 경우
1434
- error_msg = result.get("message", "알 수 없는 오류")
1435
- if isinstance(result, str):
1436
- error_msg = result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1437
 
1438
- # JavaScript 실행 코드: 오류 표시
1439
- js_code = f"""
1440
- <script>
1441
- showDeployAlert('error', '⚠️ 배포 실패', '{error_msg}');
1442
- updateDeployStatus('error', '배포 실패: {error_msg}', '');
1443
- document.getElementById('deploy-result-box').innerHTML = `
1444
- <div class="deploy-error">
1445
- <div class="error-icon">⚠️</div>
1446
- <div class="error-message">배포 실패: {error_msg}</div>
1447
- </div>
1448
- `;
1449
- </script>
1450
- """
1451
-
1452
- return js_code, {
1453
- "is_deployed": False,
1454
- "status": "error",
1455
- "message": error_msg,
1456
- "url": ""
1457
- }
1458
-
1459
  except Exception as e:
1460
  error_msg = str(e)
1461
- # JavaScript 실행 코드: 예외 표시
1462
- js_code = f"""
1463
  <script>
1464
- showDeployAlert('error', '⚠️ 시스템 오류', '{error_msg}');
1465
- updateDeployStatus('error', '시스템 오류: {error_msg}', '');
 
 
1466
  document.getElementById('deploy-result-box').innerHTML = `
1467
  <div class="deploy-error">
1468
  <div class="error-icon">⚠️</div>
@@ -1471,15 +1517,14 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
1471
  `;
1472
  </script>
1473
  """
1474
-
1475
- return js_code, {
1476
  "is_deployed": False,
1477
  "status": "error",
1478
  "message": error_msg,
1479
  "url": ""
1480
  }
1481
 
1482
- # 배포 버튼 클릭 JavaScript 트리거와 상태 업데이트
1483
  deploy_btn.click(
1484
  fn=handle_deploy,
1485
  inputs=[code_output, deploy_status],
 
945
  # 7) Gradio / Modelscope UI 빌드
946
  # ------------------------
947
 
948
+ # ------------------------
949
+ # 7) Gradio / Modelscope UI 빌드
950
+ # ------------------------
951
+
952
  demo_instance = Demo()
953
  theme = gr.themes.Soft(
954
  primary_hue="blue",
 
960
  )
961
 
962
  with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
963
+ # 헤더 HTML을 동적으로 업데이트 가능하도록 그라디오 HTML 컴포넌트로 변경
964
+ header_html = gr.HTML("""
965
+ <div class="app-header">
966
+ <h1>🎮 Vibe Game Craft</h1>
967
+ <p>설명을 입력하면 웹 기반 HTML5, JavaScript, CSS 게임을 생성합니다. 직관적인 인터페이스로 쉽게 게임을 만들고, 실시간으로 미리보기를 확인하세요.</p>
968
+ </div>
969
+
970
+ <!-- 배포 결과 박스 - 헤더 바로 아래 위치 -->
971
+ <div id="deploy-banner" style="display:none;" class="deploy-banner">
972
+ <div class="deploy-banner-content">
973
+ <div class="deploy-banner-icon">🚀</div>
974
+ <div class="deploy-banner-info">
975
+ <div id="deploy-banner-title" class="deploy-banner-title">배포 상태</div>
976
+ <div id="deploy-banner-message" class="deploy-banner-message"></div>
977
+ </div>
978
+ <div id="deploy-banner-url-container" class="deploy-banner-url-container" style="display:none;">
979
+ <a id="deploy-banner-url" href="#" target="_blank" class="deploy-banner-url"></a>
980
+ <button onclick="copyBannerUrl()" class="deploy-banner-copy-btn">복사</button>
981
+ </div>
982
+ </div>
983
+ </div>
984
+
985
  <style>
986
  /* 전체 앱 스타일 */
987
  :root {
 
1006
  .app-header {
1007
  text-align: center;
1008
  padding: 1.5rem 1rem;
1009
+ margin-bottom: 0.5rem;
1010
  background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
1011
  border-radius: var(--radius);
1012
  box-shadow: var(--shadow);
 
1027
  margin: 0 auto;
1028
  }
1029
 
1030
+ /* 배포 배너 스타일 - 헤더 바로 아래에 표시 */
1031
+ .deploy-banner {
1032
+ background: white;
1033
+ border-radius: var(--radius);
1034
+ margin: 0.5rem auto 1.5rem auto;
1035
+ box-shadow: var(--shadow);
1036
+ max-width: 1200px;
1037
+ border: 1px solid #ddd;
1038
+ overflow: hidden;
1039
+ transition: all 0.3s ease;
 
 
 
 
1040
  }
1041
 
1042
+ .deploy-banner.success {
1043
  border-left: 5px solid #34c759;
1044
  }
1045
 
1046
+ .deploy-banner.error {
1047
  border-left: 5px solid #ff3b30;
1048
  }
1049
 
1050
+ .deploy-banner.loading {
1051
+ border-left: 5px solid #007aff;
 
 
 
 
 
 
 
 
 
 
1052
  }
1053
 
1054
+ .deploy-banner-content {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1055
  display: flex;
1056
  align-items: center;
1057
+ padding: 15px 20px;
1058
  }
1059
 
1060
+ .deploy-banner-icon {
1061
+ font-size: 24px;
1062
+ margin-right: 15px;
 
 
 
 
 
1063
  }
1064
 
1065
+ .deploy-banner-info {
1066
+ flex: 1;
 
1067
  }
1068
 
1069
+ .deploy-banner-title {
1070
+ font-weight: bold;
1071
+ font-size: 16px;
1072
+ margin-bottom: 5px;
1073
  }
1074
 
1075
+ .deploy-banner-message {
1076
+ color: #666;
 
1077
  }
1078
 
1079
+ .deploy-banner-url-container {
1080
+ background: #f5f8ff;
1081
+ padding: 10px 15px;
1082
+ border-radius: 8px;
1083
+ margin-left: 20px;
1084
+ max-width: 400px;
1085
+ display: flex;
1086
+ align-items: center;
1087
  }
1088
 
1089
+ .deploy-banner-url {
 
1090
  color: #0066cc;
1091
+ text-decoration: none;
1092
+ font-weight: 500;
1093
  word-break: break-all;
1094
+ flex: 1;
1095
  }
 
1096
 
1097
+ .deploy-banner-copy-btn {
1098
+ background: #0066cc;
1099
+ color: white;
1100
+ border: none;
1101
+ border-radius: 4px;
1102
+ padding: 5px 10px;
1103
+ margin-left: 10px;
1104
+ cursor: pointer;
1105
+ font-size: 12px;
1106
+ }
1107
 
1108
+ .deploy-banner-copy-btn:hover {
1109
+ background: #0052a3;
1110
+ }
1111
+ </style>
 
 
 
 
1112
 
1113
  <script>
1114
+ // URL 복사 함수
1115
+ function copyBannerUrl() {
1116
+ const url = document.getElementById('deploy-banner-url').href;
1117
+ navigator.clipboard.writeText(url)
1118
+ .then(() => {
1119
+ // 복사 성공 시 버튼 텍스트 변경
1120
+ const copyBtn = document.querySelector('.deploy-banner-copy-btn');
1121
+ const originalText = copyBtn.textContent;
1122
+ copyBtn.textContent = '복사됨!';
1123
+
1124
+ // 1초 원래 텍스트로 복원
1125
+ setTimeout(() => {
1126
+ copyBtn.textContent = originalText;
1127
+ }, 1000);
1128
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1129
  }
1130
 
1131
+ // 배포 배너 표시 함수
1132
+ function showDeployBanner(type, title, message, url) {
1133
+ const banner = document.getElementById('deploy-banner');
1134
+ const bannerTitle = document.getElementById('deploy-banner-title');
1135
+ const bannerMessage = document.getElementById('deploy-banner-message');
1136
+ const bannerUrlContainer = document.getElementById('deploy-banner-url-container');
1137
+ const bannerUrl = document.getElementById('deploy-banner-url');
1138
+
1139
+ // 배너 클래스 설정
1140
+ banner.className = 'deploy-banner ' + type;
1141
 
1142
+ // 타이틀과 메시지 설정
1143
+ bannerTitle.textContent = title;
1144
+ bannerMessage.textContent = message;
1145
 
1146
+ // URL이 있으면 표시
1147
+ if (url) {
1148
+ bannerUrl.href = url;
1149
+ bannerUrl.textContent = url;
1150
+ bannerUrlContainer.style.display = 'flex';
 
 
 
1151
  } else {
1152
+ bannerUrlContainer.style.display = 'none';
 
1153
  }
1154
 
1155
+ // 배너 표시
1156
+ banner.style.display = 'block';
1157
  }
1158
  </script>
1159
  """)
 
1247
  )
1248
  gr.HTML('<div class="help-text">💡 원하는 게임의 설명을 입력하세요. 예: "테트리스 게임 제작해줘."</div>')
1249
 
1250
+ # ── (4) 배포 결과 영역 - JavaScript로 동적 업데이트 ──
1251
+ deploy_result_container = gr.HTML("""
1252
  <div class="deploy-section">
1253
  <div class="deploy-header">📤 배포 결과</div>
1254
  <div id="deploy-result-box" class="deploy-result-box">
 
1330
  outputs=[sandbox, state_tab]
1331
  )
1332
 
1333
+ # 수정된 배포 함수
1334
+ def deploy_to_vercel(code: str):
1335
+ """
1336
+ Vercel에 배포하는 함수 - 실제 배포 로직
1337
+ """
1338
+ try:
1339
+ token = "A8IFZmgW2cqA4yUNlLPnci0N" # 실제 토큰 필요
1340
+ if not token:
1341
+ return {"status": "error", "message": "Vercel 토큰이 설정되지 않았습니다."}
1342
+
1343
+ project_name = ''.join(random.choice(string.ascii_lowercase) for i in range(6))
1344
+ deploy_url = "https://api.vercel.com/v13/deployments"
1345
+ headers = {
1346
+ "Authorization": f"Bearer {token}",
1347
+ "Content-Type": "application/json"
1348
+ }
1349
+ package_json = {
1350
+ "name": project_name,
1351
+ "version": "1.0.0",
1352
+ "private": True,
1353
+ "dependencies": {
1354
+ "vite": "^5.0.0"
1355
+ },
1356
+ "scripts": {
1357
+ "dev": "vite",
1358
+ "build": "echo 'No build needed' && mkdir -p dist && cp index.html dist/",
1359
+ "preview": "vite preview"
1360
+ }
1361
+ }
1362
+ files = [
1363
+ {
1364
+ "file": "index.html",
1365
+ "data": code
1366
+ },
1367
+ {
1368
+ "file": "package.json",
1369
+ "data": json.dumps(package_json, indent=2)
1370
+ }
1371
+ ]
1372
+ project_settings = {
1373
+ "buildCommand": "npm run build",
1374
+ "outputDirectory": "dist",
1375
+ "installCommand": "npm install",
1376
+ "framework": None
1377
+ }
1378
+ deploy_data = {
1379
+ "name": project_name,
1380
+ "files": files,
1381
+ "target": "production",
1382
+ "projectSettings": project_settings
1383
+ }
1384
+ deploy_response = requests.post(deploy_url, headers=headers, json=deploy_data)
1385
+ if deploy_response.status_code != 200:
1386
+ return {"status": "error", "message": f"배포 실패: {deploy_response.text}"}
1387
+
1388
+ deployment_url = f"https://{project_name}.vercel.app"
1389
+ time.sleep(5)
1390
+
1391
+ return {
1392
+ "status": "success",
1393
+ "url": deployment_url,
1394
+ "project_name": project_name
1395
+ }
1396
+ except Exception as e:
1397
+ return {"status": "error", "message": f"배포 중 오류 발생: {str(e)}"}
1398
+
1399
+ # (H) '배포' 버튼 => Vercel - 헤더와 배포 결과 영역 모두 업데이트
1400
  def handle_deploy(code, deploy_status):
1401
  if not code:
1402
+ # JavaScript 오류 표시
1403
  js_code = """
1404
  <script>
1405
+ // 헤더 배너에 오류 표시
1406
+ showDeployBanner('error', '⚠️ 배포 실패', '배포할 코드가 없습니다. 먼저 게임 코드를 생성해주세요.');
1407
+
1408
+ // 결과 영역에도 오류 표시
1409
  document.getElementById('deploy-result-box').innerHTML = `
1410
  <div class="deploy-error">
1411
  <div class="error-icon">⚠️</div>
 
1414
  `;
1415
  </script>
1416
  """
 
1417
  return js_code, {
1418
  "is_deployed": False,
1419
  "status": "error",
 
1422
  }
1423
 
1424
  try:
1425
+ # 로딩 상태 표시
1426
+ loading_js = """
1427
  <script>
1428
+ // 헤더 배너에 로딩 상태 표시
1429
+ showDeployBanner('loading', '🔄 배포 진행 중', 'Vercel에 게임을 배포하고 있습니다. 잠시만 기다려주세요.');
1430
+
1431
+ // 결과 영역에도 로딩 표시
1432
  document.getElementById('deploy-result-box').innerHTML = `
1433
  <div class="deploy-loading">
1434
  <div class="loading-spinner"></div>
 
1436
  </div>
1437
  `;
1438
  </script>
1439
+ """
1440
+ # 로딩 상태를 먼저 업데이트
1441
+ yield loading_js, deploy_status
1442
 
1443
  # 코드 전처리
1444
  clean_code = remove_code_block(code)
1445
 
1446
+ # 실제 배포 실행
 
1447
  result = deploy_to_vercel(clean_code)
1448
 
1449
+ # 성공
1450
+ if result.get("status") == "success":
1451
  url = result.get("url")
1452
+ # 성공 메시지 JavaScript
1453
+ success_js = f"""
1454
  <script>
1455
+ // 헤더 배너에 성공 표시 URL 포함
1456
+ showDeployBanner('success', '배포 완료!', '게임이 성공적으로 Vercel에 배포되었습니다.', '{url}');
1457
+
1458
+ // 결과 영역에도 성공 표시
1459
  document.getElementById('deploy-result-box').innerHTML = `
1460
  <div class="deploy-success">
1461
  <div class="success-icon">✅</div>
 
1468
  `;
1469
  </script>
1470
  """
1471
+ return success_js, {
 
1472
  "is_deployed": True,
1473
  "status": "success",
1474
  "url": url,
1475
  "message": "배포 완료!"
1476
  }
1477
 
1478
+ # 실패
1479
+ else:
1480
+ error_msg = result.get("message", "알 수 없는 오류")
1481
+ # 오류 메시지 JavaScript
1482
+ error_js = f"""
1483
+ <script>
1484
+ // 헤더 배너에 오류 표시
1485
+ showDeployBanner('error', '⚠️ 배포 실패', '{error_msg}');
1486
+
1487
+ // 결과 영역에도 오류 표시
1488
+ document.getElementById('deploy-result-box').innerHTML = `
1489
+ <div class="deploy-error">
1490
+ <div class="error-icon">⚠️</div>
1491
+ <div class="error-message">배포 실패: {error_msg}</div>
1492
+ </div>
1493
+ `;
1494
+ </script>
1495
+ """
1496
+ return error_js, {
1497
+ "is_deployed": False,
1498
+ "status": "error",
1499
+ "message": error_msg,
1500
+ "url": ""
1501
+ }
1502
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1503
  except Exception as e:
1504
  error_msg = str(e)
1505
+ # 예외 메시지 JavaScript
1506
+ exception_js = f"""
1507
  <script>
1508
+ // 헤더 배너에 오류 표시
1509
+ showDeployBanner('error', '⚠️ 시스템 오류', '{error_msg}');
1510
+
1511
+ // 결과 영역에도 오류 표시
1512
  document.getElementById('deploy-result-box').innerHTML = `
1513
  <div class="deploy-error">
1514
  <div class="error-icon">⚠️</div>
 
1517
  `;
1518
  </script>
1519
  """
1520
+ return exception_js, {
 
1521
  "is_deployed": False,
1522
  "status": "error",
1523
  "message": error_msg,
1524
  "url": ""
1525
  }
1526
 
1527
+ # 배포 버튼 클릭 - JavaScript 트리거 상태 업데이트
1528
  deploy_btn.click(
1529
  fn=handle_deploy,
1530
  inputs=[code_output, deploy_status],