openfree commited on
Commit
1e80494
·
verified ·
1 Parent(s): 8d88dbd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +399 -411
app.py CHANGED
@@ -908,15 +908,13 @@ radius_size=gr.themes.sizes.radius_md,
908
  text_size=gr.themes.sizes.text_md,
909
  )
910
 
911
- # 올바른 들여쓰기를 적용한 코드 부분
912
-
913
  with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
914
  # 갤러리 CSS 추가
915
  gr.HTML("""
916
  <style>
917
  /* 갤러리 스타일 */
918
  .gallery-container {
919
- padding: 20px;
920
  }
921
 
922
  .gallery-tabs {
@@ -925,426 +923,416 @@ with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
925
  border-bottom: 1px solid #eee;
926
  }
927
 
928
- /* 나머지 CSS 스타일 유지 */
929
- </style>
930
- """)
931
-
932
-
933
- .gallery-tabs {
934
- display: flex;
935
- margin-bottom: 20px;
936
- border-bottom: 1px solid #eee;
937
- }
938
-
939
- .gallery-tab {
940
- padding: 10px 20px;
941
- cursor: pointer;
942
- font-weight: 500;
943
- border-bottom: 2px solid transparent;
944
- }
945
-
946
- .gallery-tab.active {
947
- border-color: var(--primary-color);
948
- color: var(--primary-color);
949
- }
950
-
951
- .gallery-grid {
952
- display: grid;
953
- grid-template-columns: repeat(3, 1fr);
954
- gap: 20px;
955
- margin-bottom: 30px;
956
- }
957
-
958
- .game-card {
959
- background: white;
960
- border-radius: 8px;
961
- overflow: hidden;
962
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
963
- transition: transform 0.3s ease;
964
- }
965
-
966
- .game-card:hover {
967
- transform: translateY(-5px);
968
- }
969
-
970
- .game-preview {
971
- position: relative;
972
- height: 200px;
973
- display: block;
974
- }
975
-
976
- .game-preview img {
977
- width: 100%;
978
- height: 100%;
979
- object-fit: cover;
980
- }
981
-
982
- .game-preview-overlay {
983
- position: absolute;
984
- top: 0;
985
- left: 0;
986
- width: 100%;
987
- height: 100%;
988
- background: rgba(0,0,0,0.4);
989
- display: flex;
990
- align-items: center;
991
- justify-content: center;
992
- opacity: 0;
993
- transition: opacity 0.3s ease;
994
- }
995
-
996
- .game-preview-overlay span {
997
- color: white;
998
- font-weight: bold;
999
- background: rgba(0,0,0,0.5);
1000
- padding: 8px 16px;
1001
- border-radius: 4px;
1002
- }
1003
-
1004
- .game-preview:hover .game-preview-overlay {
1005
- opacity: 1;
1006
- }
1007
-
1008
- .game-info {
1009
- padding: 15px;
1010
- }
1011
-
1012
- .game-info h3 {
1013
- margin: 0 0 10px 0;
1014
- font-size: 18px;
1015
- }
1016
-
1017
- .game-info h3 a {
1018
- color: #333;
1019
- text-decoration: none;
1020
- }
1021
-
1022
- .game-desc {
1023
- color: #666;
1024
- font-size: 14px;
1025
- margin-bottom: 15px;
1026
- display: -webkit-box;
1027
- -webkit-line-clamp: 2;
1028
- -webkit-box-orient: vertical;
1029
- overflow: hidden;
1030
- }
1031
-
1032
- .game-meta {
1033
- display: flex;
1034
- justify-content: space-between;
1035
- align-items: center;
1036
- font-size: 12px;
1037
- }
1038
-
1039
- .game-date {
1040
- color: #888;
1041
- }
1042
-
1043
- .game-play-btn {
1044
- background: var(--primary-color);
1045
- color: white;
1046
- padding: 5px 12px;
1047
- border-radius: 4px;
1048
- text-decoration: none;
1049
- font-weight: 500;
1050
- font-size: 12px;
1051
- }
1052
-
1053
- .gallery-pagination {
1054
- display: flex;
1055
- justify-content: center;
1056
- gap: 5px;
1057
- margin-top: 20px;
1058
- }
1059
-
1060
- .page-btn {
1061
- background: white;
1062
- border: 1px solid #ddd;
1063
- border-radius: 4px;
1064
- padding: 5px 10px;
1065
- cursor: pointer;
1066
- font-size: 14px;
1067
- }
1068
-
1069
- .page-btn.active {
1070
- background: var(--primary-color);
1071
- color: white;
1072
- border-color: var(--primary-color);
1073
- }
1074
-
1075
- .gallery-empty {
1076
- text-align: center;
1077
- padding: 50px 0;
1078
- color: #666;
1079
- }
1080
-
1081
- .admin-panel {
1082
- background: #f9f9f9;
1083
- padding: 20px;
1084
- border-radius: 8px;
1085
- margin-top: 30px;
1086
- }
1087
-
1088
- .admin-title {
1089
- font-size: 18px;
1090
- font-weight: bold;
1091
- margin-bottom: 15px;
1092
- }
1093
-
1094
- .admin-form {
1095
- display: grid;
1096
- grid-template-columns: 1fr 1fr 1fr auto;
1097
- gap: 10px;
1098
- align-items: center;
1099
- }
1100
-
1101
- .admin-input {
1102
- padding: 8px 12px;
1103
- border: 1px solid #ddd;
1104
- border-radius: 4px;
1105
- }
1106
-
1107
- .admin-btn {
1108
- background: var(--primary-color);
1109
- color: white;
1110
- border: none;
1111
- border-radius: 4px;
1112
- padding: 8px 12px;
1113
- cursor: pointer;
1114
- }
1115
-
1116
- /* 갤러리 에러 스타일 추가 */
1117
- .gallery-error {
1118
- padding: 20px;
1119
- background-color: #fff2f0;
1120
- border: 1px solid #ffccc7;
1121
- border-radius: 4px;
1122
- color: #f5222d;
1123
- text-align: center;
1124
- margin: 20px 0;
1125
- }
1126
-
1127
- /* 모바일 응답형 */
1128
- @media (max-width: 768px) {
1129
- .gallery-grid {
1130
- grid-template-columns: repeat(2, 1fr);
1131
  }
1132
- }
1133
-
1134
- @media (max-width: 480px) {
 
 
 
1135
  .gallery-grid {
1136
- grid-template-columns: 1fr;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1137
  }
1138
 
1139
  .admin-form {
1140
- grid-template-columns: 1fr;
 
 
 
1141
  }
1142
- }
1143
- </style>
1144
- """)
1145
-
1146
- # 헤더 HTML
1147
- header_html = gr.HTML("""
1148
- <div class="app-header">
1149
- <h1>🎮 Vibe Game Craft</h1>
1150
- <p>설명을 입력하면 웹 기반 HTML5, JavaScript, CSS 게임을 생성합니다. 직관적인 인터페이스로 쉽게 게임을 만들고, 실시간으로 미리보기를 확인하세요.</p>
1151
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1152
 
1153
- <!-- 배포 결과 박스 - 헤더 바로 아래 위치 -->
1154
- <div id="deploy-banner" style="display:none;" class="deploy-banner">
1155
- <div class="deploy-banner-content">
1156
- <div class="deploy-banner-icon">🚀</div>
1157
- <div class="deploy-banner-info">
1158
- <div id="deploy-banner-title" class="deploy-banner-title">배포 상태</div>
1159
- <div id="deploy-banner-message" class="deploy-banner-message"></div>
1160
- </div>
1161
- <div id="deploy-banner-url-container" class="deploy-banner-url-container" style="display:none;">
1162
- <a id="deploy-banner-url" href="#" target="_blank" class="deploy-banner-url"></a>
1163
- <button onclick="copyBannerUrl()" class="deploy-banner-copy-btn">복사</button>
 
 
 
 
 
 
 
 
1164
  </div>
1165
  </div>
1166
- </div>
1167
-
1168
- <style>
1169
- /* 전체 앱 스타일 */
1170
- :root {
1171
- --primary-color: #9c89b8;
1172
- --secondary-color: #f0a6ca;
1173
- --accent-color: #b8bedd;
1174
- --background-color: #f9f7fd;
1175
- --panel-color: #ffffff;
1176
- --text-color: #3a3042;
1177
- --button-hover: #efc3e6;
1178
- --shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
1179
- --radius: 12px;
1180
- }
1181
-
1182
- body {
1183
- background-color: var(--background-color);
1184
- color: var(--text-color);
1185
- font-family: 'Poppins', sans-serif;
1186
- }
1187
-
1188
- /* 헤더 스타일 */
1189
- .app-header {
1190
- text-align: center;
1191
- padding: 1.5rem 1rem;
1192
- margin-bottom: 0.5rem;
1193
- background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
1194
- border-radius: var(--radius);
1195
- box-shadow: var(--shadow);
1196
- color: white;
1197
- }
1198
-
1199
- .app-header h1 {
1200
- font-size: 2.5rem;
1201
- font-weight: 700;
1202
- margin-bottom: 0.5rem;
1203
- text-shadow: 0 2px 4px rgba(0,0,0,0.1);
1204
- }
1205
-
1206
- .app-header p {
1207
- font-size: 1.1rem;
1208
- opacity: 0.9;
1209
- max-width: 800px;
1210
- margin: 0 auto;
1211
- }
1212
-
1213
- /* 배포 배너 스타일 - 헤더 바로 아래에 표시 */
1214
- .deploy-banner {
1215
- background: white;
1216
- border-radius: var(--radius);
1217
- margin: 0.5rem auto 1.5rem auto;
1218
- box-shadow: var(--shadow);
1219
- max-width: 1200px;
1220
- border: 1px solid #ddd;
1221
- overflow: hidden;
1222
- transition: all 0.3s ease;
1223
- }
1224
-
1225
- .deploy-banner.success {
1226
- border-left: 5px solid #34c759;
1227
- }
1228
-
1229
- .deploy-banner.error {
1230
- border-left: 5px solid #ff3b30;
1231
- }
1232
-
1233
- .deploy-banner.loading {
1234
- border-left: 5px solid #007aff;
1235
- }
1236
-
1237
- .deploy-banner-content {
1238
- display: flex;
1239
- align-items: center;
1240
- padding: 15px 20px;
1241
- }
1242
-
1243
- .deploy-banner-icon {
1244
- font-size: 24px;
1245
- margin-right: 15px;
1246
- }
1247
-
1248
- .deploy-banner-info {
1249
- flex: 1;
1250
- }
1251
-
1252
- .deploy-banner-title {
1253
- font-weight: bold;
1254
- font-size: 16px;
1255
- margin-bottom: 5px;
1256
- }
1257
-
1258
- .deploy-banner-message {
1259
- color: #666;
1260
- }
1261
-
1262
- .deploy-banner-url-container {
1263
- background: #f5f8ff;
1264
- padding: 10px 15px;
1265
- border-radius: 8px;
1266
- margin-left: 20px;
1267
- max-width: 400px;
1268
- display: flex;
1269
- align-items: center;
1270
- }
1271
-
1272
- .deploy-banner-url {
1273
- color: #0066cc;
1274
- text-decoration: none;
1275
- font-weight: 500;
1276
- word-break: break-all;
1277
- flex: 1;
1278
- }
1279
-
1280
- .deploy-banner-copy-btn {
1281
- background: #0066cc;
1282
- color: white;
1283
- border: none;
1284
- border-radius: 4px;
1285
- padding: 5px 10px;
1286
- margin-left: 10px;
1287
- cursor: pointer;
1288
- font-size: 12px;
1289
- }
1290
-
1291
- .deploy-banner-copy-btn:hover {
1292
- background: #0052a3;
1293
- }
1294
- </style>
1295
-
1296
- <script>
1297
- // URL 복사 함수
1298
- function copyBannerUrl() {
1299
- const url = document.getElementById('deploy-banner-url').href;
1300
- navigator.clipboard.writeText(url)
1301
- .then(() => {
1302
- const copyBtn = document.querySelector('.deploy-banner-copy-btn');
1303
- const originalText = copyBtn.textContent;
1304
- copyBtn.textContent = '복사됨!';
1305
- setTimeout(() => {
1306
- copyBtn.textContent = originalText;
1307
- }, 1000);
1308
- });
1309
- }
1310
-
1311
- // 배포 배너 표시 함수
1312
- function showDeployBanner(type, title, message, url) {
1313
- const banner = document.getElementById('deploy-banner');
1314
- const bannerTitle = document.getElementById('deploy-banner-title');
1315
- const bannerMessage = document.getElementById('deploy-banner-message');
1316
- const bannerUrlContainer = document.getElementById('deploy-banner-url-container');
1317
- const bannerUrl = document.getElementById('deploy-banner-url');
1318
-
1319
- banner.className = 'deploy-banner ' + type;
1320
- bannerTitle.textContent = title;
1321
- bannerMessage.textContent = message;
1322
-
1323
- if (url) {
1324
- bannerUrl.href = url;
1325
- bannerUrl.textContent = url;
1326
- bannerUrlContainer.style.display = 'flex';
1327
- } else {
1328
- bannerUrlContainer.style.display = 'none';
1329
  }
1330
 
1331
- banner.style.display = 'block';
1332
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1333
 
1334
- // 갤러리 탭 변경 핸들러
1335
- function changeGalleryTab(tabName) {
1336
- document.querySelectorAll('.gallery-tab').forEach(tab => {
1337
- tab.classList.remove('active');
1338
- });
1339
- document.getElementById('tab-' + tabName).classList.add('active');
1340
-
1341
- // 이벤트 발생
1342
- document.dispatchEvent(new CustomEvent('galleryTabChange', {
1343
- detail: { tab: tabName }
1344
- }));
1345
- }
1346
- </script>
1347
- """)
1348
 
1349
  history = gr.State([])
1350
  setting = gr.State({"system": SystemPrompt})
 
908
  text_size=gr.themes.sizes.text_md,
909
  )
910
 
 
 
911
  with gr.Blocks(css_paths=["app.css"], theme=theme) as demo:
912
  # 갤러리 CSS 추가
913
  gr.HTML("""
914
  <style>
915
  /* 갤러리 스타일 */
916
  .gallery-container {
917
+ padding: 20px;
918
  }
919
 
920
  .gallery-tabs {
 
923
  border-bottom: 1px solid #eee;
924
  }
925
 
926
+ .gallery-tab {
927
+ padding: 10px 20px;
928
+ cursor: pointer;
929
+ font-weight: 500;
930
+ border-bottom: 2px solid transparent;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
931
  }
932
+
933
+ .gallery-tab.active {
934
+ border-color: var(--primary-color);
935
+ color: var(--primary-color);
936
+ }
937
+
938
  .gallery-grid {
939
+ display: grid;
940
+ grid-template-columns: repeat(3, 1fr);
941
+ gap: 20px;
942
+ margin-bottom: 30px;
943
+ }
944
+
945
+ .game-card {
946
+ background: white;
947
+ border-radius: 8px;
948
+ overflow: hidden;
949
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
950
+ transition: transform 0.3s ease;
951
+ }
952
+
953
+ .game-card:hover {
954
+ transform: translateY(-5px);
955
+ }
956
+
957
+ .game-preview {
958
+ position: relative;
959
+ height: 200px;
960
+ display: block;
961
+ }
962
+
963
+ .game-preview img {
964
+ width: 100%;
965
+ height: 100%;
966
+ object-fit: cover;
967
+ }
968
+
969
+ .game-preview-overlay {
970
+ position: absolute;
971
+ top: 0;
972
+ left: 0;
973
+ width: 100%;
974
+ height: 100%;
975
+ background: rgba(0,0,0,0.4);
976
+ display: flex;
977
+ align-items: center;
978
+ justify-content: center;
979
+ opacity: 0;
980
+ transition: opacity 0.3s ease;
981
+ }
982
+
983
+ .game-preview-overlay span {
984
+ color: white;
985
+ font-weight: bold;
986
+ background: rgba(0,0,0,0.5);
987
+ padding: 8px 16px;
988
+ border-radius: 4px;
989
+ }
990
+
991
+ .game-preview:hover .game-preview-overlay {
992
+ opacity: 1;
993
+ }
994
+
995
+ .game-info {
996
+ padding: 15px;
997
+ }
998
+
999
+ .game-info h3 {
1000
+ margin: 0 0 10px 0;
1001
+ font-size: 18px;
1002
+ }
1003
+
1004
+ .game-info h3 a {
1005
+ color: #333;
1006
+ text-decoration: none;
1007
+ }
1008
+
1009
+ .game-desc {
1010
+ color: #666;
1011
+ font-size: 14px;
1012
+ margin-bottom: 15px;
1013
+ display: -webkit-box;
1014
+ -webkit-line-clamp: 2;
1015
+ -webkit-box-orient: vertical;
1016
+ overflow: hidden;
1017
+ }
1018
+
1019
+ .game-meta {
1020
+ display: flex;
1021
+ justify-content: space-between;
1022
+ align-items: center;
1023
+ font-size: 12px;
1024
+ }
1025
+
1026
+ .game-date {
1027
+ color: #888;
1028
+ }
1029
+
1030
+ .game-play-btn {
1031
+ background: var(--primary-color);
1032
+ color: white;
1033
+ padding: 5px 12px;
1034
+ border-radius: 4px;
1035
+ text-decoration: none;
1036
+ font-weight: 500;
1037
+ font-size: 12px;
1038
+ }
1039
+
1040
+ .gallery-pagination {
1041
+ display: flex;
1042
+ justify-content: center;
1043
+ gap: 5px;
1044
+ margin-top: 20px;
1045
+ }
1046
+
1047
+ .page-btn {
1048
+ background: white;
1049
+ border: 1px solid #ddd;
1050
+ border-radius: 4px;
1051
+ padding: 5px 10px;
1052
+ cursor: pointer;
1053
+ font-size: 14px;
1054
+ }
1055
+
1056
+ .page-btn.active {
1057
+ background: var(--primary-color);
1058
+ color: white;
1059
+ border-color: var(--primary-color);
1060
+ }
1061
+
1062
+ .gallery-empty {
1063
+ text-align: center;
1064
+ padding: 50px 0;
1065
+ color: #666;
1066
+ }
1067
+
1068
+ .admin-panel {
1069
+ background: #f9f9f9;
1070
+ padding: 20px;
1071
+ border-radius: 8px;
1072
+ margin-top: 30px;
1073
+ }
1074
+
1075
+ .admin-title {
1076
+ font-size: 18px;
1077
+ font-weight: bold;
1078
+ margin-bottom: 15px;
1079
  }
1080
 
1081
  .admin-form {
1082
+ display: grid;
1083
+ grid-template-columns: 1fr 1fr 1fr auto;
1084
+ gap: 10px;
1085
+ align-items: center;
1086
  }
1087
+
1088
+ .admin-input {
1089
+ padding: 8px 12px;
1090
+ border: 1px solid #ddd;
1091
+ border-radius: 4px;
1092
+ }
1093
+
1094
+ .admin-btn {
1095
+ background: var(--primary-color);
1096
+ color: white;
1097
+ border: none;
1098
+ border-radius: 4px;
1099
+ padding: 8px 12px;
1100
+ cursor: pointer;
1101
+ }
1102
+
1103
+ /* 갤러리 에러 스타일 추가 */
1104
+ .gallery-error {
1105
+ padding: 20px;
1106
+ background-color: #fff2f0;
1107
+ border: 1px solid #ffccc7;
1108
+ border-radius: 4px;
1109
+ color: #f5222d;
1110
+ text-align: center;
1111
+ margin: 20px 0;
1112
+ }
1113
+
1114
+ /* 모바일 응답형 */
1115
+ @media (max-width: 768px) {
1116
+ .gallery-grid {
1117
+ grid-template-columns: repeat(2, 1fr);
1118
+ }
1119
+ }
1120
+
1121
+ @media (max-width: 480px) {
1122
+ .gallery-grid {
1123
+ grid-template-columns: 1fr;
1124
+ }
1125
+
1126
+ .admin-form {
1127
+ grid-template-columns: 1fr;
1128
+ }
1129
+ }
1130
+ </style>
1131
+ """)
1132
 
1133
+ # 헤더 HTML
1134
+ header_html = gr.HTML("""
1135
+ <div class="app-header">
1136
+ <h1>🎮 Vibe Game Craft</h1>
1137
+ <p>설명을 입력하면 웹 기반 HTML5, JavaScript, CSS 게임을 생성합니다. 직관적인 인터페이스로 쉽게 게임을 만들고, 실시간으로 미리보기를 확인하세요.</p>
1138
+ </div>
1139
+
1140
+ <!-- 배포 결과 박스 - 헤더 바로 아래 위치 -->
1141
+ <div id="deploy-banner" style="display:none;" class="deploy-banner">
1142
+ <div class="deploy-banner-content">
1143
+ <div class="deploy-banner-icon">🚀</div>
1144
+ <div class="deploy-banner-info">
1145
+ <div id="deploy-banner-title" class="deploy-banner-title">배포 상태</div>
1146
+ <div id="deploy-banner-message" class="deploy-banner-message"></div>
1147
+ </div>
1148
+ <div id="deploy-banner-url-container" class="deploy-banner-url-container" style="display:none;">
1149
+ <a id="deploy-banner-url" href="#" target="_blank" class="deploy-banner-url"></a>
1150
+ <button onclick="copyBannerUrl()" class="deploy-banner-copy-btn">복사</button>
1151
+ </div>
1152
  </div>
1153
  </div>
1154
+
1155
+ <style>
1156
+ /* 전체 앱 스타일 */
1157
+ :root {
1158
+ --primary-color: #9c89b8;
1159
+ --secondary-color: #f0a6ca;
1160
+ --accent-color: #b8bedd;
1161
+ --background-color: #f9f7fd;
1162
+ --panel-color: #ffffff;
1163
+ --text-color: #3a3042;
1164
+ --button-hover: #efc3e6;
1165
+ --shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
1166
+ --radius: 12px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1167
  }
1168
 
1169
+ body {
1170
+ background-color: var(--background-color);
1171
+ color: var(--text-color);
1172
+ font-family: 'Poppins', sans-serif;
1173
+ }
1174
+
1175
+ /* 헤더 스타일 */
1176
+ .app-header {
1177
+ text-align: center;
1178
+ padding: 1.5rem 1rem;
1179
+ margin-bottom: 0.5rem;
1180
+ background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
1181
+ border-radius: var(--radius);
1182
+ box-shadow: var(--shadow);
1183
+ color: white;
1184
+ }
1185
+
1186
+ .app-header h1 {
1187
+ font-size: 2.5rem;
1188
+ font-weight: 700;
1189
+ margin-bottom: 0.5rem;
1190
+ text-shadow: 0 2px 4px rgba(0,0,0,0.1);
1191
+ }
1192
+
1193
+ .app-header p {
1194
+ font-size: 1.1rem;
1195
+ opacity: 0.9;
1196
+ max-width: 800px;
1197
+ margin: 0 auto;
1198
+ }
1199
+
1200
+ /* 배포 배너 스타일 - 헤더 바로 아래에 표시 */
1201
+ .deploy-banner {
1202
+ background: white;
1203
+ border-radius: var(--radius);
1204
+ margin: 0.5rem auto 1.5rem auto;
1205
+ box-shadow: var(--shadow);
1206
+ max-width: 1200px;
1207
+ border: 1px solid #ddd;
1208
+ overflow: hidden;
1209
+ transition: all 0.3s ease;
1210
+ }
1211
+
1212
+ .deploy-banner.success {
1213
+ border-left: 5px solid #34c759;
1214
+ }
1215
+
1216
+ .deploy-banner.error {
1217
+ border-left: 5px solid #ff3b30;
1218
+ }
1219
+
1220
+ .deploy-banner.loading {
1221
+ border-left: 5px solid #007aff;
1222
+ }
1223
+
1224
+ .deploy-banner-content {
1225
+ display: flex;
1226
+ align-items: center;
1227
+ padding: 15px 20px;
1228
+ }
1229
+
1230
+ .deploy-banner-icon {
1231
+ font-size: 24px;
1232
+ margin-right: 15px;
1233
+ }
1234
+
1235
+ .deploy-banner-info {
1236
+ flex: 1;
1237
+ }
1238
+
1239
+ .deploy-banner-title {
1240
+ font-weight: bold;
1241
+ font-size: 16px;
1242
+ margin-bottom: 5px;
1243
+ }
1244
+
1245
+ .deploy-banner-message {
1246
+ color: #666;
1247
+ }
1248
+
1249
+ .deploy-banner-url-container {
1250
+ background: #f5f8ff;
1251
+ padding: 10px 15px;
1252
+ border-radius: 8px;
1253
+ margin-left: 20px;
1254
+ max-width: 400px;
1255
+ display: flex;
1256
+ align-items: center;
1257
+ }
1258
+
1259
+ .deploy-banner-url {
1260
+ color: #0066cc;
1261
+ text-decoration: none;
1262
+ font-weight: 500;
1263
+ word-break: break-all;
1264
+ flex: 1;
1265
+ }
1266
+
1267
+ .deploy-banner-copy-btn {
1268
+ background: #0066cc;
1269
+ color: white;
1270
+ border: none;
1271
+ border-radius: 4px;
1272
+ padding: 5px 10px;
1273
+ margin-left: 10px;
1274
+ cursor: pointer;
1275
+ font-size: 12px;
1276
+ }
1277
+
1278
+ .deploy-banner-copy-btn:hover {
1279
+ background: #0052a3;
1280
+ }
1281
+ </style>
1282
+
1283
+ <script>
1284
+ // URL 복사 함수
1285
+ function copyBannerUrl() {
1286
+ const url = document.getElementById('deploy-banner-url').href;
1287
+ navigator.clipboard.writeText(url)
1288
+ .then(() => {
1289
+ const copyBtn = document.querySelector('.deploy-banner-copy-btn');
1290
+ const originalText = copyBtn.textContent;
1291
+ copyBtn.textContent = '복사됨!';
1292
+ setTimeout(() => {
1293
+ copyBtn.textContent = originalText;
1294
+ }, 1000);
1295
+ });
1296
+ }
1297
+
1298
+ // 배포 배너 표시 함수
1299
+ function showDeployBanner(type, title, message, url) {
1300
+ const banner = document.getElementById('deploy-banner');
1301
+ const bannerTitle = document.getElementById('deploy-banner-title');
1302
+ const bannerMessage = document.getElementById('deploy-banner-message');
1303
+ const bannerUrlContainer = document.getElementById('deploy-banner-url-container');
1304
+ const bannerUrl = document.getElementById('deploy-banner-url');
1305
+
1306
+ banner.className = 'deploy-banner ' + type;
1307
+ bannerTitle.textContent = title;
1308
+ bannerMessage.textContent = message;
1309
+
1310
+ if (url) {
1311
+ bannerUrl.href = url;
1312
+ bannerUrl.textContent = url;
1313
+ bannerUrlContainer.style.display = 'flex';
1314
+ } else {
1315
+ bannerUrlContainer.style.display = 'none';
1316
+ }
1317
+
1318
+ banner.style.display = 'block';
1319
+ }
1320
+
1321
+ // 갤러리 탭 변경 핸들러
1322
+ function changeGalleryTab(tabName) {
1323
+ document.querySelectorAll('.gallery-tab').forEach(tab => {
1324
+ tab.classList.remove('active');
1325
+ });
1326
+ document.getElementById('tab-' + tabName).classList.add('active');
1327
+
1328
+ // 이벤트 발생
1329
+ document.dispatchEvent(new CustomEvent('galleryTabChange', {
1330
+ detail: { tab: tabName }
1331
+ }));
1332
+ }
1333
+ </script>
1334
+ """)
1335
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1336
 
1337
  history = gr.State([])
1338
  setting = gr.State({"system": SystemPrompt})