ssboost commited on
Commit
bc17107
ยท
verified ยท
1 Parent(s): 81be558

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +265 -188
app.py CHANGED
@@ -1,30 +1,110 @@
 
1
  import gradio as gr
2
  import os
3
  import tempfile
4
  from datetime import datetime
5
  from PIL import Image
 
6
 
7
- # ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ API ์—”๋“œํฌ์ธํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ (์ฝ”๋“œ์—์„œ๋Š” ์ ˆ๋Œ€ ๋…ธ์ถœ ์•ˆ๋จ)
8
- API_ENDPOINT = os.getenv('API_ENDPOINT')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- if not API_ENDPOINT:
11
- print("โŒ API_ENDPOINT ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
12
- exit(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- # ๋กœ๊ทธ ์–ต์ œ๋œ ํด๋ผ์ด์–ธํŠธ ์ž„ํฌํŠธ
15
  try:
16
- from gradio_client import Client, handle_file
17
- print("โœ… Gradio Client ์ž„ํฌํŠธ ์„ฑ๊ณต")
 
 
 
 
 
18
  except ImportError as e:
19
- print(f"โŒ Gradio Client ์ž„ํฌํŠธ ์˜ค๋ฅ˜: {e}")
20
- exit(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
- # ์ปค์Šคํ…€ CSS ์Šคํƒ€์ผ (๊ธฐ์กด๊ณผ ๋™์ผ)
23
  custom_css = """
24
  /* ============================================
25
  ๋‹คํฌ๋ชจ๋“œ ์ž๋™ ๋ณ€๊ฒฝ ํ…œํ”Œ๋ฆฟ CSS
26
  ============================================ */
27
-
28
  /* 1. CSS ๋ณ€์ˆ˜ ์ •์˜ (๋ผ์ดํŠธ๋ชจ๋“œ - ๊ธฐ๋ณธ๊ฐ’) */
29
  :root {
30
  /* ๋ฉ”์ธ ์ปฌ๋Ÿฌ */
@@ -56,7 +136,6 @@ custom_css = """
56
  /* ๊ธฐํƒ€ */
57
  --border-radius: 18px;
58
  }
59
-
60
  /* 2. ๋‹คํฌ๋ชจ๋“œ ์ƒ‰์ƒ ๋ณ€์ˆ˜ (์ž๋™ ๊ฐ์ง€) */
61
  @media (prefers-color-scheme: dark) {
62
  :root {
@@ -82,7 +161,6 @@ custom_css = """
82
  --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2);
83
  }
84
  }
85
-
86
  /* 3. ์ˆ˜๋™ ๋‹คํฌ๋ชจ๋“œ ํด๋ž˜์Šค (Gradio ํ† ๊ธ€์šฉ) */
87
  [data-theme="dark"],
88
  .dark,
@@ -108,7 +186,6 @@ custom_css = """
108
  --shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
109
  --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2);
110
  }
111
-
112
  /* 4. ๊ธฐ๋ณธ ์š”์†Œ ๋‹คํฌ๋ชจ๋“œ ์ ์šฉ */
113
  body {
114
  font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
@@ -120,7 +197,6 @@ body {
120
  font-size: 16px;
121
  transition: background-color 0.3s ease, color 0.3s ease;
122
  }
123
-
124
  /* 5. Gradio ์ปจํ…Œ์ด๋„ˆ ๊ฐ•์ œ ์ ์šฉ */
125
  .gradio-container,
126
  .gradio-container *,
@@ -130,19 +206,16 @@ body {
130
  background-color: var(--background-color) !important;
131
  color: var(--text-color) !important;
132
  }
133
-
134
  /* ํ‘ธํ„ฐ ์ˆจ๊น€ ์„ค์ • ์ถ”๊ฐ€ */
135
  footer {
136
  visibility: hidden;
137
  }
138
-
139
  .gradio-container {
140
  width: 100%;
141
  margin: 0 auto;
142
  padding: 20px;
143
  background-color: var(--background-color);
144
  }
145
-
146
  /* โ”€โ”€ ๊ทธ๋ฃน ๋ž˜ํผ ๋ฐฐ๊ฒฝ ์™„์ „ ์ œ๊ฑฐ โ”€โ”€ */
147
  .custom-section-group,
148
  .gr-block.gr-group {
@@ -156,13 +229,11 @@ footer {
156
  display: none !important;
157
  content: none !important;
158
  }
159
-
160
  /* ๊ทธ๋ฃน ์ปจํ…Œ์ด๋„ˆ ๋ฐฐ๊ฒฝ์„ ์•„์ด๋ณด๋ฆฌ๋กœ, ๊ทธ๋ฆผ์ž ์ œ๊ฑฐ */
161
  .custom-section-group {
162
  background-color: var(--background-color) !important;
163
  box-shadow: none !important;
164
  }
165
-
166
  /* 6. ์นด๋“œ ๋ฐ ํŒจ๋„ ์Šคํƒ€์ผ */
167
  .custom-frame,
168
  .gr-form,
@@ -179,7 +250,6 @@ footer {
179
  box-shadow: var(--shadow) !important;
180
  color: var(--text-color) !important;
181
  }
182
-
183
  /* ์„น์…˜ ๊ทธ๋ฃน ์Šคํƒ€์ผ - ํšŒ์ƒ‰ ๋ฐฐ๊ฒฝ ์™„์ „ ์ œ๊ฑฐ */
184
  .custom-section-group {
185
  margin-top: 20px;
@@ -189,7 +259,6 @@ footer {
189
  background-color: var(--background-color);
190
  box-shadow: none !important;
191
  }
192
-
193
  /* ๋ฒ„ํŠผ ์Šคํƒ€์ผ - ๊ธ€์ž ํฌ๊ธฐ 18px */
194
  .custom-button {
195
  border-radius: 30px !important;
@@ -205,7 +274,6 @@ footer {
205
  transform: translateY(-2px);
206
  box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3);
207
  }
208
-
209
  /* ์ œ๋ชฉ ์Šคํƒ€์ผ (๋ชจ๋“  ํ•ญ๋ชฉ๋ช…์ด ๋™์ผํ•˜๊ฒŒ custom-title ํด๋ž˜์Šค๋กœ) */
210
  .custom-title {
211
  font-size: 28px;
@@ -215,7 +283,6 @@ footer {
215
  border-bottom: 2px solid var(--primary-color);
216
  padding-bottom: 5px;
217
  }
218
-
219
  /* ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ - ํฌ๊ธฐ ๊ณ ์ • */
220
  .image-container {
221
  border-radius: var(--border-radius);
@@ -224,7 +291,6 @@ footer {
224
  transition: all 0.3s ease;
225
  background-color: var(--card-bg);
226
  }
227
-
228
  /* ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์˜์—ญ ๊ฐœ์„  */
229
  .gradio-container .gr-image {
230
  border: 2px dashed var(--border-color) !important;
@@ -232,12 +298,10 @@ footer {
232
  background-color: var(--card-bg) !important;
233
  transition: all 0.3s ease !important;
234
  }
235
-
236
  .gradio-container .gr-image:hover {
237
  border-color: var(--primary-color) !important;
238
  box-shadow: 0 4px 12px rgba(251, 127, 13, 0.15) !important;
239
  }
240
-
241
  /* ์—…๋กœ๋“œ ์˜์—ญ ๋‚ด๋ถ€ ํ…์ŠคํŠธ */
242
  .gradio-container .gr-image .upload-container,
243
  .gradio-container .gr-image [data-testid="upload-container"] {
@@ -245,14 +309,12 @@ footer {
245
  color: var(--text-color) !important;
246
  border: none !important;
247
  }
248
-
249
  /* ์—…๋กœ๋“œ ์˜์—ญ ๋“œ๋ž˜๊ทธ ์•ˆ๋‚ด ํ…์ŠคํŠธ */
250
  .gradio-container .gr-image .upload-container p,
251
  .gradio-container .gr-image [data-testid="upload-container"] p {
252
  color: var(--text-color) !important;
253
  font-size: 14px !important;
254
  }
255
-
256
  /* ์—…๋กœ๋“œ ๋ฒ„ํŠผ ์Šคํƒ€์ผ ๊ฐœ์„  */
257
  .gradio-container .gr-image .upload-container button,
258
  .gradio-container .gr-image [data-testid="upload-container"] button {
@@ -265,13 +327,11 @@ footer {
265
  cursor: pointer !important;
266
  transition: all 0.3s ease !important;
267
  }
268
-
269
  .gradio-container .gr-image .upload-container button:hover,
270
  .gradio-container .gr-image [data-testid="upload-container"] button:hover {
271
  background-color: var(--secondary-color) !important;
272
  transform: translateY(-1px) !important;
273
  }
274
-
275
  /* ์—…๋กœ๋“œ ์˜์—ญ ์•„์ด์ฝ˜ */
276
  .gradio-container .gr-image .upload-container svg,
277
  .gradio-container .gr-image [data-testid="upload-container"] svg {
@@ -279,13 +339,11 @@ footer {
279
  width: 32px !important;
280
  height: 32px !important;
281
  }
282
-
283
  /* ์ด๋ฏธ์ง€๊ฐ€ ์—…๋กœ๋“œ๋œ ํ›„ ํ‘œ์‹œ ์˜์—ญ */
284
  .gradio-container .gr-image img {
285
  background-color: var(--card-bg) !important;
286
  border-radius: var(--border-radius) !important;
287
  }
288
-
289
  /* ์ด๋ฏธ์ง€ ์ œ๊ฑฐ ๋ฒ„ํŠผ */
290
  .gradio-container .gr-image .image-container button,
291
  .gradio-container .gr-image [data-testid="image"] button {
@@ -301,13 +359,11 @@ footer {
301
  cursor: pointer !important;
302
  transition: all 0.3s ease !important;
303
  }
304
-
305
  .gradio-container .gr-image .image-container button:hover,
306
  .gradio-container .gr-image [data-testid="image"] button:hover {
307
  background-color: rgba(255, 255, 255, 1) !important;
308
  transform: scale(1.1) !important;
309
  }
310
-
311
  /* ์—…๋กœ๋“œ ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ (600x600) */
312
  .upload-image-container {
313
  width: 600px !important;
@@ -317,7 +373,6 @@ footer {
317
  max-width: 600px !important;
318
  max-height: 600px !important;
319
  }
320
-
321
  /* ์ถœ๋ ฅ ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ (700x600) */
322
  .output-image-container {
323
  width: 700px !important;
@@ -327,17 +382,14 @@ footer {
327
  max-width: 700px !important;
328
  max-height: 600px !important;
329
  }
330
-
331
  .image-container:hover {
332
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
333
  }
334
-
335
  .image-container img {
336
  width: 100% !important;
337
  height: 100% !important;
338
  object-fit: contain !important;
339
  }
340
-
341
  /* Gradio ์—…๋กœ๋“œ ์ด๋ฏธ์ง€ ์ปดํฌ๋„ŒํŠธ ํฌ๊ธฐ ๊ณ ์ • (600x600) */
342
  .gradio-container .gr-image.upload-image {
343
  width: 600px !important;
@@ -347,7 +399,6 @@ footer {
347
  max-width: 600px !important;
348
  max-height: 600px !important;
349
  }
350
-
351
  /* Gradio ์ถœ๋ ฅ ์ด๋ฏธ์ง€ ์ปดํฌ๋„ŒํŠธ ํฌ๊ธฐ ๊ณ ์ • (700x600) */
352
  .gradio-container .gr-image.output-image {
353
  width: 700px !important;
@@ -357,7 +408,6 @@ footer {
357
  max-width: 700px !important;
358
  max-height: 600px !important;
359
  }
360
-
361
  /* ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์˜์—ญ ํฌ๊ธฐ ๊ณ ์ • */
362
  .gradio-container .gr-image.upload-image > div {
363
  width: 600px !important;
@@ -367,7 +417,6 @@ footer {
367
  max-width: 600px !important;
368
  max-height: 600px !important;
369
  }
370
-
371
  /* ์ด๋ฏธ์ง€ ์ถœ๋ ฅ ์˜์—ญ ํฌ๊ธฐ ๊ณ ์ • */
372
  .gradio-container .gr-image.output-image > div {
373
  width: 700px !important;
@@ -377,7 +426,6 @@ footer {
377
  max-width: 700px !important;
378
  max-height: 600px !important;
379
  }
380
-
381
  /* ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋“œ๋ž˜๊ทธ ์˜์—ญ ํฌ๊ธฐ ๊ณ ์ • */
382
  .gradio-container .gr-image.upload-image .image-container,
383
  .gradio-container .gr-image.upload-image [data-testid="image"],
@@ -389,7 +437,6 @@ footer {
389
  max-width: 600px !important;
390
  max-height: 600px !important;
391
  }
392
-
393
  /* ์ด๋ฏธ์ง€ ์ถœ๋ ฅ ๋“œ๋ž˜๊ทธ ์˜์—ญ ํฌ๊ธฐ ๊ณ ์ • */
394
  .gradio-container .gr-image.output-image .image-container,
395
  .gradio-container .gr-image.output-image [data-testid="image"],
@@ -401,7 +448,6 @@ footer {
401
  max-width: 700px !important;
402
  max-height: 600px !important;
403
  }
404
-
405
  /* 7. ์ž…๋ ฅ ํ•„๋“œ ์Šคํƒ€์ผ */
406
  .gr-input, .gr-text-input, .gr-sample-inputs,
407
  input[type="text"],
@@ -421,7 +467,6 @@ select,
421
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important;
422
  transition: all 0.3s ease !important;
423
  }
424
-
425
  .gr-input:focus, .gr-text-input:focus,
426
  input[type="text"]:focus,
427
  input[type="number"]:focus,
@@ -435,7 +480,6 @@ select:focus,
435
  outline: none !important;
436
  box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
437
  }
438
-
439
  /* 8. ๋ผ๋ฒจ ๋ฐ ํ…์ŠคํŠธ ์š”์†Œ */
440
  .gradio-container label,
441
  label,
@@ -448,12 +492,10 @@ p, span, div {
448
  font-weight: 600 !important;
449
  margin-bottom: 8px !important;
450
  }
451
-
452
  /* ๋“œ๋กญ๋‹ค์šด ๋ฐ ๋ผ๋””์˜ค ๋ฒ„ํŠผ ํฐํŠธ ํฌ๊ธฐ */
453
  .gr-radio label, .gr-dropdown label, .gr-checkbox label {
454
  font-size: 15px !important;
455
  }
456
-
457
  /* ๋ผ๋””์˜ค ๋ฒ„ํŠผ ์„ ํƒ์ง€ ๋ณผ๋“œ ์ฒ˜๋ฆฌ ์ œ๊ฑฐ */
458
  .gr-radio .gr-radio-option label,
459
  .gr-radio input[type="radio"] + label,
@@ -461,26 +503,22 @@ p, span, div {
461
  font-weight: normal !important;
462
  font-size: 15px !important;
463
  }
464
-
465
  /* ๋ผ๋””์˜ค ๋ฒ„ํŠผ ๊ทธ๋ฃน ๋‚ด ๋ชจ๋“  ๋ผ๋ฒจ ์ผ๋ฐ˜ ํฐํŠธ๋กœ ์„ค์ • */
466
  .gr-radio fieldset label {
467
  font-weight: normal !important;
468
  }
469
-
470
  /* ๋งˆํฌ๋‹ค์šด ํ…์ŠคํŠธ ํฌ๊ธฐ ์ฆ๊ฐ€ */
471
  .gradio-container .gr-markdown {
472
  font-size: 15px !important;
473
  line-height: 1.6 !important;
474
  color: var(--text-color) !important;
475
  }
476
-
477
  /* ํ…์ŠคํŠธ๋ฐ•์Šค ๋‚ด์šฉ ํฐํŠธ ํฌ๊ธฐ */
478
  .gr-textbox textarea, .gr-textbox input {
479
  font-size: 15px !important;
480
  background-color: var(--input-bg) !important;
481
  color: var(--text-color) !important;
482
  }
483
-
484
  /* ์•„์ฝ”๋””์–ธ ์ œ๋ชฉ ํฐํŠธ ํฌ๊ธฐ */
485
  .gr-accordion summary {
486
  font-size: 17px !important;
@@ -488,44 +526,36 @@ p, span, div {
488
  background-color: var(--card-bg) !important;
489
  color: var(--text-color) !important;
490
  }
491
-
492
  /* ๋ฉ”์ธ ์ปจํ…์ธ  ์Šคํฌ๋กค๋ฐ” */
493
  ::-webkit-scrollbar {
494
  width: 8px;
495
  height: 8px;
496
  }
497
-
498
  ::-webkit-scrollbar-track {
499
  background: var(--card-bg);
500
  border-radius: 10px;
501
  }
502
-
503
  ::-webkit-scrollbar-thumb {
504
  background: var(--primary-color);
505
  border-radius: 10px;
506
  }
507
-
508
  ::-webkit-scrollbar-thumb:hover {
509
  background: var(--secondary-color);
510
  }
511
-
512
  /* ์• ๋‹ˆ๋ฉ”์ด์…˜ ์Šคํƒ€์ผ */
513
  @keyframes fadeIn {
514
  from { opacity: 0; transform: translateY(10px); }
515
  to { opacity: 1; transform: translateY(0); }
516
  }
517
-
518
  .fade-in {
519
  animation: fadeIn 0.5s ease-out;
520
  }
521
-
522
  /* ๋ฐ˜์‘ํ˜• */
523
  @media (max-width: 768px) {
524
  .button-grid {
525
  grid-template-columns: repeat(2, 1fr);
526
  }
527
  }
528
-
529
  /* ์„น์…˜ ์ œ๋ชฉ ์Šคํƒ€์ผ */
530
  .section-title {
531
  display: flex;
@@ -538,7 +568,6 @@ p, span, div {
538
  border-bottom: 2px solid var(--primary-color);
539
  font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
540
  }
541
-
542
  .section-title img {
543
  margin-right: 12px;
544
  width: 28px;
@@ -546,21 +575,18 @@ p, span, div {
546
  /* ๋‹คํฌ๋ชจ๋“œ์—์„œ ์•„์ด์ฝ˜ ํ•„ํ„ฐ ์ ์šฉ */
547
  filter: brightness(0) saturate(100%) invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg) brightness(104%) contrast(97%);
548
  }
549
-
550
  /* ๋ผ์ดํŠธ๋ชจ๋“œ์—์„œ๋Š” ์›๋ž˜ ์•„์ด๏ฟฝ๏ฟฝ๏ฟฝ ์ƒ‰์ƒ ์œ ์ง€ */
551
  @media (prefers-color-scheme: light) {
552
  .section-title img {
553
  filter: none;
554
  }
555
  }
556
-
557
  /* ์ˆ˜๋™ ๋‹คํฌ๋ชจ๋“œ ํด๋ž˜์Šค์—์„œ๋„ ์•„์ด์ฝ˜ ์ƒ‰์ƒ ์ ์šฉ */
558
  [data-theme="dark"] .section-title img,
559
  .dark .section-title img,
560
  .gr-theme-dark .section-title img {
561
  filter: brightness(0) saturate(100%) invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg) brightness(104%) contrast(97%);
562
  }
563
-
564
  /* 10. ์•„์ฝ”๋””์–ธ ๋ฐ ๋“œ๋กญ๋‹ค์šด - ์ˆ˜๋™์„ค์ • ์˜์—ญ ํšŒ์ƒ‰ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ */
565
  details,
566
  .gr-accordion,
@@ -571,7 +597,6 @@ details,
571
  border-radius: var(--border-radius) !important;
572
  margin: 10px 0 !important;
573
  }
574
-
575
  details summary,
576
  .gr-accordion summary,
577
  .gr-accordion details summary {
@@ -584,20 +609,17 @@ details summary,
584
  font-weight: 600 !important;
585
  transition: all 0.3s ease !important;
586
  }
587
-
588
  details summary:hover,
589
  .gr-accordion summary:hover,
590
  .gr-accordion details summary:hover {
591
  background-color: var(--table-hover-bg) !important;
592
  }
593
-
594
  /* ์•„์ฝ”๋””์–ธ ๋‚ด๋ถ€ ์ฝ˜ํ…์ธ  */
595
  details[open],
596
  .gr-accordion[open],
597
  .gr-accordion details[open] {
598
  background-color: var(--background-color) !important;
599
  }
600
-
601
  details[open] > *:not(summary),
602
  .gr-accordion[open] > *:not(summary),
603
  .gr-accordion details[open] > *:not(summary) {
@@ -606,7 +628,6 @@ details[open] > *:not(summary),
606
  padding: 16px !important;
607
  border-top: 1px solid var(--border-color) !important;
608
  }
609
-
610
  /* ๊ทธ๋ฃน ๋‚ด๋ถ€ ์Šคํƒ€์ผ - ์ˆ˜๋™์„ค์ • ๋‚ด๋ถ€ ๊ทธ๋ฃน๋“ค */
611
  .gr-group,
612
  details .gr-group,
@@ -617,7 +638,6 @@ details .gr-group,
617
  margin: 8px 0 !important;
618
  border-radius: var(--border-radius) !important;
619
  }
620
-
621
  /* ๊ทธ๋ฃน ๋‚ด๋ถ€ ์ œ๋ชฉ */
622
  .gr-group .gr-markdown h3,
623
  .gr-group h3 {
@@ -628,7 +648,6 @@ details .gr-group,
628
  padding-bottom: 6px !important;
629
  border-bottom: 1px solid var(--border-color) !important;
630
  }
631
-
632
  /* 11. ์ถ”๊ฐ€ Gradio ์ปดํฌ๋„ŒํŠธ๋“ค */
633
  .gr-block,
634
  .gr-group,
@@ -637,23 +656,12 @@ details .gr-group,
637
  background-color: var(--background-color) !important;
638
  color: var(--text-color) !important;
639
  }
640
-
641
  /* 12. ๋ฒ„ํŠผ์€ ๊ธฐ์กด ์Šคํƒ€์ผ ์œ ์ง€ (primary-color ์‚ฌ์šฉ) */
642
  button:not([class*="custom"]):not([class*="primary"]):not([class*="secondary"]) {
643
  background-color: var(--card-bg) !important;
644
  color: var(--text-color) !important;
645
  border-color: var(--border-color) !important;
646
  }
647
-
648
- /* 13. ์ฝ”๋“œ ๋ธ”๋ก ๋ฐ pre ํƒœ๊ทธ */
649
- code,
650
- pre,
651
- .code-block {
652
- background-color: var(--table-even-bg) !important;
653
- color: var(--text-color) !important;
654
- border-color: var(--border-color) !important;
655
- }
656
-
657
  /* 14. ์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ */
658
  * {
659
  transition: background-color 0.3s ease,
@@ -719,22 +727,13 @@ def prepare_download_file(image, keyword):
719
  image_to_save.save(file_path, 'JPEG', quality=95)
720
 
721
  print(f"โœ… ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ ์ค€๋น„: {filename}")
 
722
  return file_path
723
 
724
  except Exception as e:
725
  print(f"โŒ ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ ์ค€๋น„ ์‹คํŒจ: {e}")
726
  return None
727
 
728
- # ๐ŸŽฏ ํ•ต์‹ฌ: ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ ํ•จ์ˆ˜ (๋กœ๊ทธ ์ตœ์†Œํ™”)
729
- def get_client():
730
- """ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ธฐ๋ฐ˜ ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ"""
731
- try:
732
- client = Client(API_ENDPOINT)
733
- return client
734
- except Exception as e:
735
- print(f"โŒ ์—ฐ๊ฒฐ ์‹คํŒจ: {str(e)[:50]}...")
736
- return None
737
-
738
  def main():
739
  with gr.Blocks(
740
  css=custom_css,
@@ -927,116 +926,194 @@ def main():
927
  interactive=False
928
  )
929
 
930
- # ๐ŸŽฏ ํ•ต์‹ฌ: ์—”๋“œํฌ์ธํŠธ ๊ธฐ๋ฐ˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋“ค
931
-
932
- def update_copy_type_description(selected_type):
933
- """์›๋ณธ๊ณผ ๋™์ผํ•œ ์นดํ”ผ ํƒ€์ž… ์„ ํƒ์‹œ ์„ค๋ช… ์—…๋ฐ์ดํŠธ"""
934
- try:
935
- client = get_client()
936
- if not client:
937
- return "โŒ ์„œ๋ฒ„ ์—ฐ๊ฒฐ ์‹คํŒจ"
938
-
939
- result = client.predict(
940
- selected_type=selected_type,
941
- api_name="/update_copy_type_description"
942
- )
943
- return result
944
- except Exception as e:
945
- print(f"โŒ ์นดํ”ผ ํƒ€์ž… ์„ค๋ช… ์—…๋ฐ์ดํŠธ ์‹คํŒจ: {str(e)[:50]}...")
946
- return f"โŒ ์˜ค๋ฅ˜: ์„ค๋ช… ์—…๋ฐ์ดํŠธ ์‹คํŒจ"
947
 
948
  def handle_copy_generation(keyword, selected_type):
949
- """์›๋ณธ๊ณผ ๋™์ผํ•œ ์นดํ”ผ ์ƒ์„ฑ ์ฒ˜๋ฆฌ"""
950
- try:
951
- client = get_client()
952
- if not client:
953
- return ("โŒ ์„œ๋ฒ„ ์—ฐ๊ฒฐ ์‹คํŒจ", {}, "", "", "", "", "", "", "", keyword.strip() if keyword else "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
954
 
955
- result = client.predict(
956
- keyword=keyword,
957
- selected_type=selected_type,
958
- api_name="/handle_copy_generation"
959
- )
960
 
961
- # ์›๋ณธ๊ณผ ๋™์ผํ•œ ํŠœํ”Œ ๋ฐ˜ํ™˜ (10๊ฐœ ์š”์†Œ)
962
- # [0] ์ƒํƒœ๋ฉ”์‹œ์ง€, [1] copy_suggestions_state, [2-6] ์นดํ”ผ1-5, [7-8] ๋ฉ”์ธ/์„œ๋ธŒ, [9] current_keyword_state
963
- if len(result) >= 8:
964
- # API์—์„œ 8๊ฐœ๋ฅผ ๋ฐ›์œผ๋ฉด ์ถ”๊ฐ€ ์ƒํƒœ๊ฐ’๋“ค์„ ๋กœ์ปฌ์—์„œ ์ฒ˜๋ฆฌ
965
- return (result[0], {}, result[1], result[2], result[3], result[4], result[5], result[6], result[7], keyword.strip() if keyword else "")
966
- else:
967
- return result
 
968
 
969
- except Exception as e:
970
- print(f"โŒ ์นดํ”ผ ์ƒ์„ฑ ์‹คํŒจ: {str(e)[:50]}...")
971
- error_msg = "โŒ ์นดํ”ผ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค"
972
- return (error_msg, {}, "", "", "", "", "", "", "", keyword.strip() if keyword else "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
973
 
974
- def handle_copy_selection(copy_suggestions_state, selected_type, selected_copy):
975
- """์›๋ณธ๊ณผ ๋™์ผํ•œ ์นดํ”ผ ์„ ํƒ์‹œ ๋ฉ”์ธ/์„œ๋ธŒ ํ…์ŠคํŠธ๋ฐ•์Šค ์—…๋ฐ์ดํŠธ"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
976
  try:
977
- client = get_client()
978
- if not client:
979
- return ("", "")
980
-
981
- result = client.predict(
982
- selected_type=selected_type,
983
- selected_copy=selected_copy,
984
- api_name="/handle_copy_selection"
985
- )
 
 
 
 
 
 
986
 
987
- # ํŠœํ”Œ ๋ฐ˜ํ™˜ (2๊ฐœ ์š”์†Œ)
988
- return result
989
 
990
  except Exception as e:
991
- print(f"โŒ ์นดํ”ผ ์„ ํƒ ์‹คํŒจ: {str(e)[:50]}...")
992
- return ("", "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
993
 
994
  def handle_image_generation(input_image, main_text, sub_text, color_mode,
995
  main_font_choice, sub_font_choice, manual_bg_color, manual_main_text_color,
996
  manual_sub_text_color, manual_main_font_size, manual_sub_font_size,
997
  top_bottom_margin, text_gap, current_keyword):
998
- """์›๋ณธ๊ณผ ๋™์ผํ•œ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ฒ˜๋ฆฌ (์—ฌ๋ฐฑ ์กฐ์ • ๊ธฐ๋Šฅ ํฌํ•จ)"""
999
- try:
1000
- client = get_client()
1001
- if not client:
1002
- return (None, color_mode, manual_bg_color, manual_main_text_color,
1003
- manual_sub_text_color, manual_main_font_size, manual_sub_font_size,
1004
- None, "โŒ ์„œ๋ฒ„ ์—ฐ๊ฒฐ ์‹คํŒจ")
1005
-
1006
- # ์ด๋ฏธ์ง€ ํŒŒ์ผ ํ•ธ๋“ค๋ง
1007
- image_file = None
1008
- if input_image:
1009
- image_file = handle_file(input_image)
1010
-
1011
- result = client.predict(
1012
- input_image=image_file,
1013
- main_text=main_text,
1014
- sub_text=sub_text,
1015
- color_mode=color_mode,
1016
- main_font_choice=main_font_choice,
1017
- sub_font_choice=sub_font_choice,
1018
- manual_bg_color=manual_bg_color,
1019
- manual_main_text_color=manual_main_text_color,
1020
- manual_sub_text_color=manual_sub_text_color,
1021
- manual_main_font_size=manual_main_font_size,
1022
- manual_sub_font_size=manual_sub_font_size,
1023
- top_bottom_margin=top_bottom_margin,
1024
- text_gap=text_gap,
1025
- api_name="/handle_image_generation"
1026
- )
1027
-
1028
- # ์›๋ณธ๊ณผ ๋™์ผํ•œ ํŠœํ”Œ ๋ฐ˜ํ™˜ (9๊ฐœ ์š”์†Œ)
1029
- # [0] ์ด๋ฏธ์ง€, [1] ์ƒ‰์ƒ๋ชจ๋“œ, [2] ๋ฐฐ๊ฒฝ์ƒ‰, [3] ๋ฉ”์ธํ…์ŠคํŠธ์ƒ‰, [4] ์„œ๋ธŒํ…์ŠคํŠธ์ƒ‰,
1030
- # [5] ๋ฉ”์ธํฐํŠธํฌ๊ธฐ, [6] ์„œ๋ธŒํฐํŠธํฌ๊ธฐ, [7] ๋‹ค์šด๋กœ๋“œํŒŒ์ผ, [8] ์—ฌ๋ฐฑ์ •๋ณด
1031
- return result
1032
-
1033
- except Exception as e:
1034
- print(f"โŒ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ: {str(e)[:50]}...")
1035
- return (None, color_mode, manual_bg_color, manual_main_text_color,
1036
- manual_sub_text_color, manual_main_font_size, manual_sub_font_size,
1037
- None, "โŒ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค")
1038
 
1039
- # ์›๋ณธ๊ณผ ๋™์ผํ•œ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
1040
  copy_type_selection.change(
1041
  fn=update_copy_type_description,
1042
  inputs=[copy_type_selection],
 
1
+ #app.py
2
  import gradio as gr
3
  import os
4
  import tempfile
5
  from datetime import datetime
6
  from PIL import Image
7
+ import random
8
 
9
+ # ๐Ÿ”‘ ํ—ˆ๊น…ํŽ˜์ด์Šค Secrets์—์„œ API ํ‚ค๋“ค ๊ฐ€์ ธ์™€์„œ ๋žœ๋ค ์„ ํƒ
10
+ def get_random_gemini_api_key():
11
+ """ํ—ˆ๊น…ํŽ˜์ด์Šค Secrets์—์„œ API ํ‚ค ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ ธ์™€ ๋žœ๋ค ์„ ํƒ (๋””๋ฒ„๊น… ๊ฐ•ํ™”)"""
12
+ try:
13
+ print("๐Ÿ” API ํ‚ค ๋กœ๋”ฉ ์‹œ์ž‘...")
14
+
15
+ # Secrets์—์„œ API ํ‚ค ๋ฆฌ์ŠคํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ (์ฝค๋งˆ๋กœ ๊ตฌ๋ถ„๋œ ๋ฌธ์ž์—ด)
16
+ api_keys_string = os.environ.get('GEMINI_API_KEYS')
17
+ print(f"๐Ÿ” GEMINI_API_KEYS ์›๋ณธ: {api_keys_string[:50] if api_keys_string else 'None'}...")
18
+
19
+ if api_keys_string:
20
+ # ์ฝค๋งˆ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ  ๊ณต๋ฐฑ ์ œ๊ฑฐ
21
+ api_keys = [key.strip() for key in api_keys_string.split(',') if key.strip()]
22
+ print(f"๐Ÿ” ํŒŒ์‹ฑ๋œ ํ‚ค ๊ฐœ์ˆ˜: {len(api_keys)}")
23
+
24
+ for i, key in enumerate(api_keys):
25
+ print(f" ํ‚ค {i+1}: {key[:8]}***{key[-4:] if len(key) > 12 else '***'} (๊ธธ์ด: {len(key)})")
26
+
27
+ if api_keys:
28
+ # ๋žœ๋คํ•˜๊ฒŒ ํ•˜๋‚˜ ์„ ํƒ
29
+ selected_key = random.choice(api_keys)
30
+ print(f"๐ŸŽฒ ๋žœ๋ค API ํ‚ค ์„ ํƒ: {selected_key[:8]}***{selected_key[-4:]}")
31
+ return selected_key
32
+ else:
33
+ print("โŒ ํŒŒ์‹ฑ๋œ ํ‚ค๊ฐ€ ์—†์Œ")
34
+ else:
35
+ print("โŒ GEMINI_API_KEYS ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์—†์Œ")
36
+
37
+ # ํด๋ฐฑ: ๋‹จ์ผ ํ‚ค
38
+ fallback_key = os.environ.get('GEMINI_API_KEY')
39
+ if fallback_key:
40
+ print(f"๐Ÿ”‘ ํด๋ฐฑ API ํ‚ค ์‚ฌ์šฉ: {fallback_key[:8]}***{fallback_key[-4:]}")
41
+ return fallback_key
42
+
43
+ print("โŒ ๋ชจ๋“  API ํ‚ค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค")
44
+ return None
45
+
46
+ except Exception as e:
47
+ print(f"โŒ API ํ‚ค ์„ ํƒ ์‹คํŒจ: {e}")
48
+ return os.environ.get('GEMINI_API_KEY')
49
 
50
+ # ํ—ˆ๊น…ํŽ˜์ด์Šค Secrets์—์„œ ๋ชจ๋“ˆ ์ฝ”๋“œ ๋ถˆ๋Ÿฌ์™€์„œ ์‹คํ–‰
51
+ def load_module_from_env(env_var_name):
52
+ """ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ์ฝ”๋“œ๋ฅผ ๋ถˆ๋Ÿฌ์™€์„œ ๋ชจ๋“ˆ๋กœ ์‹คํ–‰"""
53
+ try:
54
+ code = os.environ.get(env_var_name)
55
+ if not code:
56
+ raise ImportError(f"Environment variable '{env_var_name}' not found")
57
+
58
+ # ๋ชจ๋“ˆ namespace ์ƒ์„ฑํ•˜๊ณ  ์ฝ”๋“œ ์‹คํ–‰
59
+ module_globals = {}
60
+ exec(code, module_globals)
61
+
62
+ print(f"โœ… {env_var_name} ๋ชจ๋“ˆ ๋กœ๋“œ ์„ฑ๊ณต")
63
+ return module_globals
64
+
65
+ except Exception as e:
66
+ print(f"โŒ {env_var_name} ๋ชจ๋“ˆ ๋กœ๋“œ ์‹คํŒจ: {e}")
67
+ return None
68
 
69
+ # ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ์ž„ํฌํŠธ
70
  try:
71
+ image_processor_module = load_module_from_env('IMAGE_PROCESSOR_CODE')
72
+ if image_processor_module:
73
+ create_uhp_image = image_processor_module['create_uhp_image']
74
+ save_image_to_downloads = image_processor_module['save_image_to_downloads']
75
+ print("โœ… image_processor ๋ชจ๋“ˆ ์ž„ํฌํŠธ ์„ฑ๊ณต")
76
+ else:
77
+ raise ImportError("IMAGE_PROCESSOR_CODE not found")
78
  except ImportError as e:
79
+ print(f"โŒ image_processor ์ž„ํฌํŠธ ์˜ค๋ฅ˜: {e}")
80
+
81
+ def create_uhp_image(*args, **kwargs):
82
+ return None, "์ถ”์ฒœ๋ฐฐ๊ฒฝ", "#FFFFFF", "#000000", "#000000", 100, 55, {}
83
+
84
+ def save_image_to_downloads(*args, **kwargs):
85
+ return None, "image_processor ๋ชจ๋“ˆ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
86
+
87
+ # ์นดํ”ผ ์ƒ์„ฑ ๋ชจ๋“ˆ ์ž„ํฌํŠธ
88
+ try:
89
+ copy_generator_module = load_module_from_env('COPY_GENERATOR_CODE')
90
+ if copy_generator_module:
91
+ generate_copy_suggestions = copy_generator_module['generate_copy_suggestions']
92
+ print("โœ… copy_generator ๋ชจ๋“ˆ ์ž„ํฌํŠธ ์„ฑ๊ณต")
93
+ else:
94
+ raise ImportError("COPY_GENERATOR_CODE not found")
95
+ except ImportError as e:
96
+ print(f"โš ๏ธ copy_generator ์ž„ํฌํŠธ ์˜ค๋ฅ˜: {e}")
97
+
98
+ def generate_copy_suggestions(keyword, api_key, selected_type):
99
+ return {
100
+ "error": "copy_generator ๋ชจ๋“ˆ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์นดํ”ผ ์ƒ์„ฑ ๊ธฐ๋Šฅ์ด ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค."
101
+ }, ""
102
 
103
+ # ์ปค์Šคํ…€ CSS ์Šคํƒ€์ผ (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ ๋ฒ„์ „)
104
  custom_css = """
105
  /* ============================================
106
  ๋‹คํฌ๋ชจ๋“œ ์ž๋™ ๋ณ€๊ฒฝ ํ…œํ”Œ๋ฆฟ CSS
107
  ============================================ */
 
108
  /* 1. CSS ๋ณ€์ˆ˜ ์ •์˜ (๋ผ์ดํŠธ๋ชจ๋“œ - ๊ธฐ๋ณธ๊ฐ’) */
109
  :root {
110
  /* ๋ฉ”์ธ ์ปฌ๋Ÿฌ */
 
136
  /* ๊ธฐํƒ€ */
137
  --border-radius: 18px;
138
  }
 
139
  /* 2. ๋‹คํฌ๋ชจ๋“œ ์ƒ‰์ƒ ๋ณ€์ˆ˜ (์ž๋™ ๊ฐ์ง€) */
140
  @media (prefers-color-scheme: dark) {
141
  :root {
 
161
  --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2);
162
  }
163
  }
 
164
  /* 3. ์ˆ˜๋™ ๋‹คํฌ๋ชจ๋“œ ํด๋ž˜์Šค (Gradio ํ† ๊ธ€์šฉ) */
165
  [data-theme="dark"],
166
  .dark,
 
186
  --shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
187
  --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2);
188
  }
 
189
  /* 4. ๊ธฐ๋ณธ ์š”์†Œ ๋‹คํฌ๋ชจ๋“œ ์ ์šฉ */
190
  body {
191
  font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
 
197
  font-size: 16px;
198
  transition: background-color 0.3s ease, color 0.3s ease;
199
  }
 
200
  /* 5. Gradio ์ปจํ…Œ์ด๋„ˆ ๊ฐ•์ œ ์ ์šฉ */
201
  .gradio-container,
202
  .gradio-container *,
 
206
  background-color: var(--background-color) !important;
207
  color: var(--text-color) !important;
208
  }
 
209
  /* ํ‘ธํ„ฐ ์ˆจ๊น€ ์„ค์ • ์ถ”๊ฐ€ */
210
  footer {
211
  visibility: hidden;
212
  }
 
213
  .gradio-container {
214
  width: 100%;
215
  margin: 0 auto;
216
  padding: 20px;
217
  background-color: var(--background-color);
218
  }
 
219
  /* โ”€โ”€ ๊ทธ๋ฃน ๋ž˜ํผ ๋ฐฐ๊ฒฝ ์™„์ „ ์ œ๊ฑฐ โ”€โ”€ */
220
  .custom-section-group,
221
  .gr-block.gr-group {
 
229
  display: none !important;
230
  content: none !important;
231
  }
 
232
  /* ๊ทธ๋ฃน ์ปจํ…Œ์ด๋„ˆ ๋ฐฐ๊ฒฝ์„ ์•„์ด๋ณด๋ฆฌ๋กœ, ๊ทธ๋ฆผ์ž ์ œ๊ฑฐ */
233
  .custom-section-group {
234
  background-color: var(--background-color) !important;
235
  box-shadow: none !important;
236
  }
 
237
  /* 6. ์นด๋“œ ๋ฐ ํŒจ๋„ ์Šคํƒ€์ผ */
238
  .custom-frame,
239
  .gr-form,
 
250
  box-shadow: var(--shadow) !important;
251
  color: var(--text-color) !important;
252
  }
 
253
  /* ์„น์…˜ ๊ทธ๋ฃน ์Šคํƒ€์ผ - ํšŒ์ƒ‰ ๋ฐฐ๊ฒฝ ์™„์ „ ์ œ๊ฑฐ */
254
  .custom-section-group {
255
  margin-top: 20px;
 
259
  background-color: var(--background-color);
260
  box-shadow: none !important;
261
  }
 
262
  /* ๋ฒ„ํŠผ ์Šคํƒ€์ผ - ๊ธ€์ž ํฌ๊ธฐ 18px */
263
  .custom-button {
264
  border-radius: 30px !important;
 
274
  transform: translateY(-2px);
275
  box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3);
276
  }
 
277
  /* ์ œ๋ชฉ ์Šคํƒ€์ผ (๋ชจ๋“  ํ•ญ๋ชฉ๋ช…์ด ๋™์ผํ•˜๊ฒŒ custom-title ํด๋ž˜์Šค๋กœ) */
278
  .custom-title {
279
  font-size: 28px;
 
283
  border-bottom: 2px solid var(--primary-color);
284
  padding-bottom: 5px;
285
  }
 
286
  /* ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ - ํฌ๊ธฐ ๊ณ ์ • */
287
  .image-container {
288
  border-radius: var(--border-radius);
 
291
  transition: all 0.3s ease;
292
  background-color: var(--card-bg);
293
  }
 
294
  /* ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์˜์—ญ ๊ฐœ์„  */
295
  .gradio-container .gr-image {
296
  border: 2px dashed var(--border-color) !important;
 
298
  background-color: var(--card-bg) !important;
299
  transition: all 0.3s ease !important;
300
  }
 
301
  .gradio-container .gr-image:hover {
302
  border-color: var(--primary-color) !important;
303
  box-shadow: 0 4px 12px rgba(251, 127, 13, 0.15) !important;
304
  }
 
305
  /* ์—…๋กœ๋“œ ์˜์—ญ ๋‚ด๋ถ€ ํ…์ŠคํŠธ */
306
  .gradio-container .gr-image .upload-container,
307
  .gradio-container .gr-image [data-testid="upload-container"] {
 
309
  color: var(--text-color) !important;
310
  border: none !important;
311
  }
 
312
  /* ์—…๋กœ๋“œ ์˜์—ญ ๋“œ๋ž˜๊ทธ ์•ˆ๋‚ด ํ…์ŠคํŠธ */
313
  .gradio-container .gr-image .upload-container p,
314
  .gradio-container .gr-image [data-testid="upload-container"] p {
315
  color: var(--text-color) !important;
316
  font-size: 14px !important;
317
  }
 
318
  /* ์—…๋กœ๋“œ ๋ฒ„ํŠผ ์Šคํƒ€์ผ ๊ฐœ์„  */
319
  .gradio-container .gr-image .upload-container button,
320
  .gradio-container .gr-image [data-testid="upload-container"] button {
 
327
  cursor: pointer !important;
328
  transition: all 0.3s ease !important;
329
  }
 
330
  .gradio-container .gr-image .upload-container button:hover,
331
  .gradio-container .gr-image [data-testid="upload-container"] button:hover {
332
  background-color: var(--secondary-color) !important;
333
  transform: translateY(-1px) !important;
334
  }
 
335
  /* ์—…๋กœ๋“œ ์˜์—ญ ์•„์ด์ฝ˜ */
336
  .gradio-container .gr-image .upload-container svg,
337
  .gradio-container .gr-image [data-testid="upload-container"] svg {
 
339
  width: 32px !important;
340
  height: 32px !important;
341
  }
 
342
  /* ์ด๋ฏธ์ง€๊ฐ€ ์—…๋กœ๋“œ๋œ ํ›„ ํ‘œ์‹œ ์˜์—ญ */
343
  .gradio-container .gr-image img {
344
  background-color: var(--card-bg) !important;
345
  border-radius: var(--border-radius) !important;
346
  }
 
347
  /* ์ด๋ฏธ์ง€ ์ œ๊ฑฐ ๋ฒ„ํŠผ */
348
  .gradio-container .gr-image .image-container button,
349
  .gradio-container .gr-image [data-testid="image"] button {
 
359
  cursor: pointer !important;
360
  transition: all 0.3s ease !important;
361
  }
 
362
  .gradio-container .gr-image .image-container button:hover,
363
  .gradio-container .gr-image [data-testid="image"] button:hover {
364
  background-color: rgba(255, 255, 255, 1) !important;
365
  transform: scale(1.1) !important;
366
  }
 
367
  /* ์—…๋กœ๋“œ ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ (600x600) */
368
  .upload-image-container {
369
  width: 600px !important;
 
373
  max-width: 600px !important;
374
  max-height: 600px !important;
375
  }
 
376
  /* ์ถœ๋ ฅ ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ (700x600) */
377
  .output-image-container {
378
  width: 700px !important;
 
382
  max-width: 700px !important;
383
  max-height: 600px !important;
384
  }
 
385
  .image-container:hover {
386
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
387
  }
 
388
  .image-container img {
389
  width: 100% !important;
390
  height: 100% !important;
391
  object-fit: contain !important;
392
  }
 
393
  /* Gradio ์—…๋กœ๋“œ ์ด๋ฏธ์ง€ ์ปดํฌ๋„ŒํŠธ ํฌ๊ธฐ ๊ณ ์ • (600x600) */
394
  .gradio-container .gr-image.upload-image {
395
  width: 600px !important;
 
399
  max-width: 600px !important;
400
  max-height: 600px !important;
401
  }
 
402
  /* Gradio ์ถœ๋ ฅ ์ด๋ฏธ์ง€ ์ปดํฌ๋„ŒํŠธ ํฌ๊ธฐ ๊ณ ์ • (700x600) */
403
  .gradio-container .gr-image.output-image {
404
  width: 700px !important;
 
408
  max-width: 700px !important;
409
  max-height: 600px !important;
410
  }
 
411
  /* ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์˜์—ญ ํฌ๊ธฐ ๊ณ ์ • */
412
  .gradio-container .gr-image.upload-image > div {
413
  width: 600px !important;
 
417
  max-width: 600px !important;
418
  max-height: 600px !important;
419
  }
 
420
  /* ์ด๋ฏธ์ง€ ์ถœ๋ ฅ ์˜์—ญ ํฌ๊ธฐ ๊ณ ์ • */
421
  .gradio-container .gr-image.output-image > div {
422
  width: 700px !important;
 
426
  max-width: 700px !important;
427
  max-height: 600px !important;
428
  }
 
429
  /* ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋“œ๋ž˜๊ทธ ์˜์—ญ ํฌ๊ธฐ ๊ณ ์ • */
430
  .gradio-container .gr-image.upload-image .image-container,
431
  .gradio-container .gr-image.upload-image [data-testid="image"],
 
437
  max-width: 600px !important;
438
  max-height: 600px !important;
439
  }
 
440
  /* ์ด๋ฏธ์ง€ ์ถœ๋ ฅ ๋“œ๋ž˜๊ทธ ์˜์—ญ ํฌ๊ธฐ ๊ณ ์ • */
441
  .gradio-container .gr-image.output-image .image-container,
442
  .gradio-container .gr-image.output-image [data-testid="image"],
 
448
  max-width: 700px !important;
449
  max-height: 600px !important;
450
  }
 
451
  /* 7. ์ž…๋ ฅ ํ•„๋“œ ์Šคํƒ€์ผ */
452
  .gr-input, .gr-text-input, .gr-sample-inputs,
453
  input[type="text"],
 
467
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important;
468
  transition: all 0.3s ease !important;
469
  }
 
470
  .gr-input:focus, .gr-text-input:focus,
471
  input[type="text"]:focus,
472
  input[type="number"]:focus,
 
480
  outline: none !important;
481
  box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
482
  }
 
483
  /* 8. ๋ผ๋ฒจ ๋ฐ ํ…์ŠคํŠธ ์š”์†Œ */
484
  .gradio-container label,
485
  label,
 
492
  font-weight: 600 !important;
493
  margin-bottom: 8px !important;
494
  }
 
495
  /* ๋“œ๋กญ๋‹ค์šด ๋ฐ ๋ผ๋””์˜ค ๋ฒ„ํŠผ ํฐํŠธ ํฌ๊ธฐ */
496
  .gr-radio label, .gr-dropdown label, .gr-checkbox label {
497
  font-size: 15px !important;
498
  }
 
499
  /* ๋ผ๋””์˜ค ๋ฒ„ํŠผ ์„ ํƒ์ง€ ๋ณผ๋“œ ์ฒ˜๋ฆฌ ์ œ๊ฑฐ */
500
  .gr-radio .gr-radio-option label,
501
  .gr-radio input[type="radio"] + label,
 
503
  font-weight: normal !important;
504
  font-size: 15px !important;
505
  }
 
506
  /* ๋ผ๋””์˜ค ๋ฒ„ํŠผ ๊ทธ๋ฃน ๋‚ด ๋ชจ๋“  ๋ผ๋ฒจ ์ผ๋ฐ˜ ํฐํŠธ๋กœ ์„ค์ • */
507
  .gr-radio fieldset label {
508
  font-weight: normal !important;
509
  }
 
510
  /* ๋งˆํฌ๋‹ค์šด ํ…์ŠคํŠธ ํฌ๊ธฐ ์ฆ๊ฐ€ */
511
  .gradio-container .gr-markdown {
512
  font-size: 15px !important;
513
  line-height: 1.6 !important;
514
  color: var(--text-color) !important;
515
  }
 
516
  /* ํ…์ŠคํŠธ๋ฐ•์Šค ๋‚ด์šฉ ํฐํŠธ ํฌ๊ธฐ */
517
  .gr-textbox textarea, .gr-textbox input {
518
  font-size: 15px !important;
519
  background-color: var(--input-bg) !important;
520
  color: var(--text-color) !important;
521
  }
 
522
  /* ์•„์ฝ”๋””์–ธ ์ œ๋ชฉ ํฐํŠธ ํฌ๊ธฐ */
523
  .gr-accordion summary {
524
  font-size: 17px !important;
 
526
  background-color: var(--card-bg) !important;
527
  color: var(--text-color) !important;
528
  }
 
529
  /* ๋ฉ”์ธ ์ปจํ…์ธ  ์Šคํฌ๋กค๋ฐ” */
530
  ::-webkit-scrollbar {
531
  width: 8px;
532
  height: 8px;
533
  }
 
534
  ::-webkit-scrollbar-track {
535
  background: var(--card-bg);
536
  border-radius: 10px;
537
  }
 
538
  ::-webkit-scrollbar-thumb {
539
  background: var(--primary-color);
540
  border-radius: 10px;
541
  }
 
542
  ::-webkit-scrollbar-thumb:hover {
543
  background: var(--secondary-color);
544
  }
 
545
  /* ์• ๋‹ˆ๋ฉ”์ด์…˜ ์Šคํƒ€์ผ */
546
  @keyframes fadeIn {
547
  from { opacity: 0; transform: translateY(10px); }
548
  to { opacity: 1; transform: translateY(0); }
549
  }
 
550
  .fade-in {
551
  animation: fadeIn 0.5s ease-out;
552
  }
 
553
  /* ๋ฐ˜์‘ํ˜• */
554
  @media (max-width: 768px) {
555
  .button-grid {
556
  grid-template-columns: repeat(2, 1fr);
557
  }
558
  }
 
559
  /* ์„น์…˜ ์ œ๋ชฉ ์Šคํƒ€์ผ */
560
  .section-title {
561
  display: flex;
 
568
  border-bottom: 2px solid var(--primary-color);
569
  font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
570
  }
 
571
  .section-title img {
572
  margin-right: 12px;
573
  width: 28px;
 
575
  /* ๋‹คํฌ๋ชจ๋“œ์—์„œ ์•„์ด์ฝ˜ ํ•„ํ„ฐ ์ ์šฉ */
576
  filter: brightness(0) saturate(100%) invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg) brightness(104%) contrast(97%);
577
  }
 
578
  /* ๋ผ์ดํŠธ๋ชจ๋“œ์—์„œ๋Š” ์›๋ž˜ ์•„์ด๏ฟฝ๏ฟฝ๏ฟฝ ์ƒ‰์ƒ ์œ ์ง€ */
579
  @media (prefers-color-scheme: light) {
580
  .section-title img {
581
  filter: none;
582
  }
583
  }
 
584
  /* ์ˆ˜๋™ ๋‹คํฌ๋ชจ๋“œ ํด๋ž˜์Šค์—์„œ๋„ ์•„์ด์ฝ˜ ์ƒ‰์ƒ ์ ์šฉ */
585
  [data-theme="dark"] .section-title img,
586
  .dark .section-title img,
587
  .gr-theme-dark .section-title img {
588
  filter: brightness(0) saturate(100%) invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg) brightness(104%) contrast(97%);
589
  }
 
590
  /* 10. ์•„์ฝ”๋””์–ธ ๋ฐ ๋“œ๋กญ๋‹ค์šด - ์ˆ˜๋™์„ค์ • ์˜์—ญ ํšŒ์ƒ‰ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ */
591
  details,
592
  .gr-accordion,
 
597
  border-radius: var(--border-radius) !important;
598
  margin: 10px 0 !important;
599
  }
 
600
  details summary,
601
  .gr-accordion summary,
602
  .gr-accordion details summary {
 
609
  font-weight: 600 !important;
610
  transition: all 0.3s ease !important;
611
  }
 
612
  details summary:hover,
613
  .gr-accordion summary:hover,
614
  .gr-accordion details summary:hover {
615
  background-color: var(--table-hover-bg) !important;
616
  }
 
617
  /* ์•„์ฝ”๋””์–ธ ๋‚ด๋ถ€ ์ฝ˜ํ…์ธ  */
618
  details[open],
619
  .gr-accordion[open],
620
  .gr-accordion details[open] {
621
  background-color: var(--background-color) !important;
622
  }
 
623
  details[open] > *:not(summary),
624
  .gr-accordion[open] > *:not(summary),
625
  .gr-accordion details[open] > *:not(summary) {
 
628
  padding: 16px !important;
629
  border-top: 1px solid var(--border-color) !important;
630
  }
 
631
  /* ๊ทธ๋ฃน ๋‚ด๋ถ€ ์Šคํƒ€์ผ - ์ˆ˜๋™์„ค์ • ๋‚ด๋ถ€ ๊ทธ๋ฃน๋“ค */
632
  .gr-group,
633
  details .gr-group,
 
638
  margin: 8px 0 !important;
639
  border-radius: var(--border-radius) !important;
640
  }
 
641
  /* ๊ทธ๋ฃน ๋‚ด๋ถ€ ์ œ๋ชฉ */
642
  .gr-group .gr-markdown h3,
643
  .gr-group h3 {
 
648
  padding-bottom: 6px !important;
649
  border-bottom: 1px solid var(--border-color) !important;
650
  }
 
651
  /* 11. ์ถ”๊ฐ€ Gradio ์ปดํฌ๋„ŒํŠธ๋“ค */
652
  .gr-block,
653
  .gr-group,
 
656
  background-color: var(--background-color) !important;
657
  color: var(--text-color) !important;
658
  }
 
659
  /* 12. ๋ฒ„ํŠผ์€ ๊ธฐ์กด ์Šคํƒ€์ผ ์œ ์ง€ (primary-color ์‚ฌ์šฉ) */
660
  button:not([class*="custom"]):not([class*="primary"]):not([class*="secondary"]) {
661
  background-color: var(--card-bg) !important;
662
  color: var(--text-color) !important;
663
  border-color: var(--border-color) !important;
664
  }
 
 
 
 
 
 
 
 
 
 
665
  /* 14. ์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ */
666
  * {
667
  transition: background-color 0.3s ease,
 
727
  image_to_save.save(file_path, 'JPEG', quality=95)
728
 
729
  print(f"โœ… ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ ์ค€๋น„: {filename}")
730
+ print(f"๐Ÿ“ ํŒŒ์ผ ๊ฒฝ๋กœ: {file_path}")
731
  return file_path
732
 
733
  except Exception as e:
734
  print(f"โŒ ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ ์ค€๋น„ ์‹คํŒจ: {e}")
735
  return None
736
 
 
 
 
 
 
 
 
 
 
 
737
  def main():
738
  with gr.Blocks(
739
  css=custom_css,
 
926
  interactive=False
927
  )
928
 
929
+ # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋“ค
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
930
 
931
  def handle_copy_generation(keyword, selected_type):
932
+ """์นดํ”ผ ์ƒ์„ฑ ์ฒ˜๋ฆฌ (๋žœ๋ค API ํ‚ค ์‚ฌ์šฉ)"""
933
+ # ๐ŸŽฒ ๋žœ๋ค API ํ‚ค ์„ ํƒ
934
+ api_key = get_random_gemini_api_key()
935
+ print(f"๐Ÿ”‘ ์นดํ”ผ ์ƒ์„ฑ์šฉ API ํ‚ค: {api_key[:8] if api_key else 'None'}***")
936
+
937
+ if not keyword.strip():
938
+ return ("โš ๏ธ ์ƒํ’ˆ ํ‚ค์›Œ๋“œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ๏ฟฝ๏ฟฝ๏ฟฝ.", {}, "", "", "", "", "", "", "", keyword.strip())
939
+
940
+ if not api_key:
941
+ return ("โš ๏ธ API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.", {}, "", "", "", "", "", "", "", keyword.strip())
942
+
943
+ if not selected_type:
944
+ return ("โš ๏ธ ์นดํ”ผ ํƒ€์ž…์„ ๋จผ์ € ์„ ํƒํ•ด์ฃผ์„ธ์š”.", {}, "", "", "", "", "", "", "", keyword.strip())
945
+
946
+ print(f"๐Ÿš€ ์นดํ”ผ ์ƒ์„ฑ ํ”„๋กœ์„ธ์Šค ์‹œ์ž‘: {keyword} - {selected_type}")
947
+ suggestions, analysis = generate_copy_suggestions(keyword, api_key, selected_type)
948
+
949
+ print(f"๐Ÿ” AI ์‘๋‹ต ํƒ€์ž…: {type(suggestions)}")
950
+ print(f"๐Ÿ” AI ์‘๋‹ต ํ‚ค๋“ค: {list(suggestions.keys()) if isinstance(suggestions, dict) else 'Not dict'}")
951
+
952
+ if "error" not in suggestions:
953
+ print("โœ… ์นดํ”ผ ์ƒ์„ฑ ์„ฑ๊ณต!")
954
 
955
+ # ์„ ํƒ๋œ ํƒ€์ž…์˜ ์นดํ”ผ ๋ฆฌ์ŠคํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ
956
+ copy_list = suggestions.get(selected_type, [])
957
+ print(f"๐Ÿ“‹ {selected_type} ์นดํ”ผ ๊ฐœ์ˆ˜: {len(copy_list)}")
 
 
958
 
959
+ # ๊ฐ ์นดํ”ผ ์•„์ดํ…œ ๊ฒ€์ฆ
960
+ for i, item in enumerate(copy_list):
961
+ if isinstance(item, dict):
962
+ main = item.get("main", "")
963
+ sub = item.get("sub", "")
964
+ print(f" ์ถ”์ฒœ{i+1}: ๋ฉ”์ธ='{main}', ์„œ๋ธŒ='{sub}'")
965
+ else:
966
+ print(f" ์ถ”์ฒœ{i+1}: ์ž˜๋ชป๋œ ํ˜•์‹ {type(item)}")
967
 
968
+ # ํ‘œ์‹œ์šฉ ์นดํ”ผ ๋ฌธ์ž์—ด ์ƒ์„ฑ (์ถ”์ฒœ1~5 ํ…์ŠคํŠธ๋ฐ•์Šค์šฉ)
969
+ copy_values = []
970
+ for i in range(5):
971
+ if i < len(copy_list) and isinstance(copy_list[i], dict):
972
+ main = copy_list[i].get("main", "")
973
+ sub = copy_list[i].get("sub", "")
974
+ combined = f"{main} / {sub}"
975
+ copy_values.append(combined)
976
+ print(f"๐Ÿ“ ์ถ”์ฒœ{i+1} ํ‘œ์‹œ: {combined}")
977
+ else:
978
+ copy_values.append("")
979
+ print(f"๐Ÿ“ ์ถ”์ฒœ{i+1} ํ‘œ์‹œ: (๋น„์–ด์žˆ์Œ)")
980
+
981
+ # ์ฒซ ๋ฒˆ์งธ ์นดํ”ผ๋ฅผ ๋ฉ”์ธ/์„œ๋ธŒ ํ…์ŠคํŠธ๋ฐ•์Šค์— ์ž๋™ ์ž…๋ ฅ
982
+ first_main = ""
983
+ first_sub = ""
984
+ if copy_list and isinstance(copy_list[0], dict):
985
+ first_main = copy_list[0].get("main", "")
986
+ first_sub = copy_list[0].get("sub", "")
987
+ print(f"๐ŸŽฏ ์ž๋™ ์„ ํƒ: ๋ฉ”์ธ='{first_main}', ์„œ๋ธŒ='{first_sub}'")
988
+
989
+ return (f"โœ… {selected_type} ์นดํ”ผ ์ƒ์„ฑ ์™„๋ฃŒ!", suggestions, *copy_values, first_main, first_sub, keyword.strip())
990
+ else:
991
+ print(f"โŒ ์นดํ”ผ ์ƒ์„ฑ ์‹คํŒจ: {suggestions['error']}")
992
+ error_msg = f"โŒ ์˜ค๋ฅ˜: {suggestions['error']}"
993
+ return (error_msg, {}, "", "", "", "", "", "", "", keyword.strip())
994
+
995
+ def update_copy_type_description(selected_type):
996
+ """์นดํ”ผ ํƒ€์ž… ์„ ํƒ์‹œ ์„ค๋ช… ์—…๋ฐ์ดํŠธ"""
997
+ descriptions = {
998
+ "์žฅ์ ์š”์•ฝํ˜•": "**์žฅ์ ์š”์•ฝํ˜•**: ์ œํ’ˆ์˜ ์žฅ์ ์„ ํ•œ๋ˆˆ์— ๊ฐ•์กฐ - ํ•ต์‹ฌ ๊ธฐ๋Šฅ๊ณผ ํ˜œํƒ์„ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์š”์•ฝํ•˜์—ฌ ์ œ์‹œ",
999
+ "๋ฌธ์ œ์ œ์‹œํ˜•": "**๋ฌธ์ œ์ œ์‹œํ˜•**: ๋ฌธ์ œ๋ฅผ ์ œ์‹œ ํ›„ ํ•ด๊ฒฐ์ฑ… ์ œ์•ˆ - ๊ณ ๊ฐ์˜ ๋ถˆํŽธํ•จ์„ ๋จผ์ € ์–ธ๊ธ‰ํ•˜๊ณ  ํ•ด๊ฒฐ๋ฐฉ์•ˆ ์ œ์‹œ",
1000
+ "์‚ฌํšŒ์ ์ฆ๊ฑฐํ˜•": "**์‚ฌํšŒ์ ์ฆ๊ฑฐํ˜•**: ์‹ ๋ขฐ์™€ ์ธ๊ธฐ๋ฅผ ๊ฐ•์กฐ - ํƒ€์ธ์˜ ์‚ฌ์šฉํ›„๊ธฐ์™€ ๊ฒ€์ฆ๋œ ์‹ค์ ์œผ๋กœ ์‹ ๋ขฐ์„ฑ ์–ดํ•„",
1001
+ "๊ธด๊ธ‰์„ฑ์œ ๋„ํ˜•": "**๊ธด๊ธ‰์„ฑ์œ ๋„ํ˜•**: ์ฆ‰์‹œ ๊ตฌ๋งค๋ฅผ ์œ ๋„ - ํ•œ์ •์ˆ˜๋Ÿ‰, ์‹œ๊ฐ„์ œํ•œ ๋“ฑ์œผ๋กœ ๊ธด๊ธ‰๊ฐ ์กฐ์„ฑ",
1002
+ "๊ฐ€๊ฒฉ๊ฒฝ์Ÿ๋ ฅํ˜•": "**๊ฐ€๊ฒฉ๊ฒฝ์Ÿ๋ ฅํ˜•**: ํ•ฉ๋ฆฌ์  ๊ฐ€๊ฒฉ์„ ๊ฐ•์กฐํ•ด ์„ฑ์ทจ - ๊ฐ€์„ฑ๋น„, ํ• ์ธ๏ฟฝ๏ฟฝํƒ ๋“ฑ ๊ฒฝ์ œ์  ์ด๋“ ๊ฐ•์กฐ",
1003
+ "๋งค์ธ๋ณ€ํ™”ํ˜•": "**๋งค์ธ๋ณ€ํ™”ํ˜•**: ๊ฐ•ํ•œ ๋น„๊ต๋ฅผ ์‹œ์„  ์ง‘์ค‘ - ์‚ฌ์šฉ ์ „ํ›„ ๋ณ€ํ™”๋‚˜ ๊ทน์ ์ธ ๊ฐœ์„  ํšจ๊ณผ ๋ถ€๊ฐ",
1004
+ "์ถฉ๋™๊ตฌ๋งค์œ ๋„ํ˜•": "**์ถฉ๋™๊ตฌ๋งค์œ ๋„ํ˜•**: ํŠน์ • ๊ตฌ๋งค์š•๊ตฌ๋ฅผ ์ž๊ทน - ํŠน๋ณ„ํ•จ๊ณผ ํ”„๋ฆฌ๋ฏธ์—„ ๊ฐ€์น˜๋กœ ์†Œ์œ ์š• ์ž๊ทน",
1005
+ "๊ณตํฌ์†Œ๊ตฌํ˜•": "**๊ณตํฌ์†Œ๊ตฌํ˜•**: ๋ถˆ์•ˆ ์œ„ํ—˜์„ ๊ฐ•์กฐํ•ด ๊ฐ์ • ์œ ๋ฐœ - ๋†“์น˜๋ฉด ํ›„ํšŒํ•  ๊ธฐํšŒ๋‚˜ ์œ„ํ—˜์„ฑ์„ ๊ฒฝ๊ณ "
1006
+ }
1007
+
1008
+ if selected_type and selected_type in descriptions:
1009
+ return descriptions[selected_type]
1010
+ else:
1011
+ return "### ์นดํ”ผ ํƒ€์ž…์„ ์„ ํƒํ•˜๋ฉด ์„ค๋ช…์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค."
1012
 
1013
+ def handle_copy_selection(suggestions, selected_type, selected_copy):
1014
+ """์นดํ”ผ ์„ ํƒ์‹œ ๋ฉ”์ธ/์„œ๋ธŒ ํ…์ŠคํŠธ๋ฐ•์Šค ์—…๋ฐ์ดํŠธ (๊ฐœ์„ ๋œ ๋ฒ„์ „)"""
1015
+ print(f"๐Ÿ”˜ ๋ผ๋””์˜ค ์„ ํƒ: {selected_copy}, ํƒ€์ž…: {selected_type}")
1016
+ print(f"๐Ÿ” suggestions ์ƒํƒœ: {type(suggestions)}, ํ‚ค: {list(suggestions.keys()) if suggestions else 'None'}")
1017
+
1018
+ if not suggestions or "error" in suggestions:
1019
+ print("โŒ suggestions ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๊ฑฐ๋‚˜ ์—๋Ÿฌ ์ƒํƒœ")
1020
+ return "", ""
1021
+
1022
+ if not selected_type or not selected_copy:
1023
+ print("โŒ ํƒ€์ž…์ด๋‚˜ ์„ ํƒ๋œ ์นดํ”ผ๊ฐ€ ์—†์Œ")
1024
+ return "", ""
1025
+
1026
+ if selected_type not in suggestions:
1027
+ print(f"โŒ {selected_type} ํƒ€์ž…์„ suggestions์—์„œ ์ฐพ์„ ์ˆ˜ ์—†์Œ")
1028
+ print(f"๐Ÿ“‹ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํƒ€์ž…๋“ค: {list(suggestions.keys())}")
1029
+ return "", ""
1030
+
1031
+ copy_list = suggestions[selected_type]
1032
+ if not isinstance(copy_list, list):
1033
+ print(f"โŒ {selected_type} ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฆฌ์ŠคํŠธ๊ฐ€ ์•„๋‹˜: {type(copy_list)}")
1034
+ return "", ""
1035
+
1036
+ print(f"๐Ÿ“ {selected_type} ์นดํ”ผ ๋ฆฌ์ŠคํŠธ: {len(copy_list)}๊ฐœ")
1037
+
1038
+ # ๋ผ๋””์˜ค ๋ฒ„ํŠผ ์„ ํƒ๊ฐ’์—์„œ ๋ฒˆํ˜ธ ์ถ”์ถœ (๋” ์•ˆ์ „ํ•œ ๋ฐฉ๋ฒ•)
1039
  try:
1040
+ if "์ถ”์ฒœ 1" in selected_copy:
1041
+ option_number = 0
1042
+ elif "์ถ”์ฒœ 2" in selected_copy:
1043
+ option_number = 1
1044
+ elif "์ถ”์ฒœ 3" in selected_copy:
1045
+ option_number = 2
1046
+ elif "์ถ”์ฒœ 4" in selected_copy:
1047
+ option_number = 3
1048
+ elif "์ถ”์ฒœ 5" in selected_copy:
1049
+ option_number = 4
1050
+ else:
1051
+ # ๋ฐฑ์—… ๋ฐฉ๋ฒ•: ์ˆซ์ž ์ถ”์ถœ
1052
+ import re
1053
+ numbers = re.findall(r'\d+', selected_copy)
1054
+ option_number = int(numbers[0]) - 1 if numbers else 0
1055
 
1056
+ print(f"๐Ÿ”ข ์ถ”์ถœ๋œ ์˜ต์…˜ ๋ฒˆํ˜ธ: {option_number}")
 
1057
 
1058
  except Exception as e:
1059
+ print(f"โŒ ์˜ต์…˜ ๋ฒˆํ˜ธ ์ถ”์ถœ ์‹คํŒจ: {e}, ๊ธฐ๋ณธ๊ฐ’ 0 ์‚ฌ์šฉ")
1060
+ option_number = 0
1061
+
1062
+ # ๋ฒ”์œ„ ์ฒดํฌ
1063
+ if 0 <= option_number < len(copy_list):
1064
+ selected_copy_item = copy_list[option_number]
1065
+
1066
+ if not isinstance(selected_copy_item, dict):
1067
+ print(f"โŒ ์„ ํƒ๋œ ์นดํ”ผ ์•„์ดํ…œ์ด ๋”•์…”๋„ˆ๋ฆฌ๊ฐ€ ์•„๋‹˜: {type(selected_copy_item)}")
1068
+ return "", ""
1069
+
1070
+ main_text = selected_copy_item.get("main", "")
1071
+ sub_text = selected_copy_item.get("sub", "")
1072
+
1073
+ print(f"โœ… ์„ ํƒ๋œ ์นดํ”ผ - ๋ฉ”์ธ: '{main_text}', ์„œ๋ธŒ: '{sub_text}'")
1074
+
1075
+ # ๋นˆ ๋ฌธ์ž์—ด ์ฒดํฌ
1076
+ if not main_text and not sub_text:
1077
+ print("โš ๏ธ ๋ฉ”์ธ์นดํ”ผ์™€ ์„œ๋ธŒ์นดํ”ผ๊ฐ€ ๋ชจ๋‘ ๋น„์–ด์žˆ์Œ")
1078
+
1079
+ return main_text, sub_text
1080
+ else:
1081
+ print(f"โŒ ์ž˜๋ชป๋œ ์˜ต์…˜ ๋ฒˆํ˜ธ: {option_number} (๋ฒ”์œ„: 0~{len(copy_list)-1})")
1082
+ return "", ""
1083
 
1084
  def handle_image_generation(input_image, main_text, sub_text, color_mode,
1085
  main_font_choice, sub_font_choice, manual_bg_color, manual_main_text_color,
1086
  manual_sub_text_color, manual_main_font_size, manual_sub_font_size,
1087
  top_bottom_margin, text_gap, current_keyword):
1088
+ """์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ฒ˜๋ฆฌ (์—ฌ๋ฐฑ ์กฐ์ • ๊ธฐ๋Šฅ ํฌํ•จ)"""
1089
+ # ๐ŸŽฒ ๋žœ๋ค API ํ‚ค ์„ ํƒ
1090
+ api_key = get_random_gemini_api_key()
1091
+ print(f"๐Ÿ”‘ ์ด๋ฏธ์ง€ ์ƒ์„ฑ์šฉ API ํ‚ค: {api_key[:8] if api_key else 'None'}***")
1092
+
1093
+ # ๐ŸŽฏ NEW: ์—ฌ๋ฐฑ ์„ค์ •์„ create_uhp_image ํ•จ์ˆ˜์— ์ „๋‹ฌ
1094
+ image_result, color_mode_result, new_bg_color, new_main_text_color, new_sub_text_color, new_main_font_size, new_sub_font_size, applied_margin_info = create_uhp_image(
1095
+ input_image, main_text, sub_text, color_mode,
1096
+ main_font_choice, sub_font_choice, manual_bg_color, manual_main_text_color, manual_sub_text_color,
1097
+ manual_main_font_size, manual_sub_font_size, api_key,
1098
+ top_bottom_margin, text_gap # ์ƒˆ๋กœ์šด ์—ฌ๋ฐฑ ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€
1099
+ )
1100
+
1101
+ # ๐ŸŽฏ ํ•ต์‹ฌ: ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ ์ค€๋น„
1102
+ download_file_path = None
1103
+ if image_result is not None:
1104
+ download_file_path = prepare_download_file(image_result, current_keyword)
1105
+
1106
+ # ๐ŸŽฏ NEW: ์ ์šฉ๋œ ์—ฌ๋ฐฑ ์ •๋ณด ์ƒ์„ฑ
1107
+ margin_info_text = f"""๐Ÿ“ **์ ์šฉ๋œ ์—ฌ๋ฐฑ ์ •๋ณด:**
1108
+ โ€ข ์ƒํ•˜ ์—ฌ๋ฐฑ: {applied_margin_info.get('top_bottom_margin', 0)}px
1109
+ โ€ข ๋ฉ”์ธโ†”์„œ๋ธŒ ๊ฐ„๊ฒฉ: {applied_margin_info.get('text_gap', 0)}px
1110
+ โ€ข ์ด๋ฏธ์ง€ ํฌ๊ธฐ: {applied_margin_info.get('canvas_width', 0)} ร— {applied_margin_info.get('canvas_height', 0)}px
1111
+ โ€ข ์›๋ณธ ํฌ๊ธฐ: {applied_margin_info.get('original_width', 0)} ร— {applied_margin_info.get('original_height', 0)}px"""
1112
+
1113
+ return (image_result, color_mode_result, new_bg_color, new_main_text_color, new_sub_text_color,
1114
+ new_main_font_size, new_sub_font_size, download_file_path, margin_info_text)
 
 
 
 
 
 
 
 
 
 
 
 
 
1115
 
1116
+ # ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
1117
  copy_type_selection.change(
1118
  fn=update_copy_type_description,
1119
  inputs=[copy_type_selection],