aiqtech commited on
Commit
5f609da
·
verified ·
1 Parent(s): 750b2db

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +221 -398
app.py CHANGED
@@ -1,437 +1,260 @@
1
  import gradio as gr
2
- import numpy as np
3
- from PIL import Image, ImageDraw, ImageFont
4
- from gradio_client import Client, handle_file
5
- import random
6
- import tempfile
7
- import os
8
- import logging
9
- import torch
10
- from diffusers import AutoencoderKL, TCDScheduler
11
- from diffusers.models.model_loading_utils import load_state_dict
12
- from huggingface_hub import hf_hub_download
13
- from pathlib import Path
14
- import torchaudio
15
- from einops import rearrange
16
- from scipy.io import wavfile
17
- from transformers import pipeline
18
- import cv2
19
 
20
- # 환경 변수 설정으로 torch.load 체크 우회 (임시 해결책)
21
- os.environ["TRANSFORMERS_ALLOW_UNSAFE_DESERIALIZATION"] = "1"
22
-
23
- # Spaces GPU - Hugging Face Spaces compatibility
24
- try:
25
- import spaces
26
- SPACES_GPU_AVAILABLE = True
27
- except ImportError:
28
- SPACES_GPU_AVAILABLE = False
29
- # GPU 데코레이터가 없을 때를 위한 더미 데코레이터
30
- class spaces:
31
- @staticmethod
32
- def GPU(duration=None):
33
- def decorator(func):
34
- return func
35
- return decorator
36
-
37
- # API URLs
38
- TEXT2IMG_API_URL = "http://211.233.58.201:7896"
39
- VIDEO_API_URL = "http://211.233.58.201:7875"
40
-
41
- # 로깅 설정
42
- logging.basicConfig(level=logging.INFO)
43
-
44
- # Image size presets
45
- IMAGE_PRESETS = {
46
- "Custom": {"width": 1024, "height": 1024},
47
- "1:1 Square": {"width": 1024, "height": 1024},
48
- "4:3 Standard": {"width": 1024, "height": 768},
49
- "16:9 Widescreen": {"width": 1024, "height": 576},
50
- "9:16 Portrait": {"width": 576, "height": 1024},
51
- "6:19 Ultra Tall": {"width": 324, "height": 1024},
52
- "Instagram Square": {"width": 1080, "height": 1080},
53
- "Instagram Story": {"width": 1080, "height": 1920},
54
- "Instagram Landscape": {"width": 1080, "height": 566},
55
- "Facebook Cover": {"width": 820, "height": 312},
56
- "Twitter Header": {"width": 1500, "height": 500},
57
- "YouTube Thumbnail": {"width": 1280, "height": 720},
58
- "LinkedIn Banner": {"width": 1584, "height": 396},
59
  }
60
 
61
- def update_dimensions(preset):
62
- if preset in IMAGE_PRESETS:
63
- return IMAGE_PRESETS[preset]["width"], IMAGE_PRESETS[preset]["height"]
64
- return 1024, 1024
65
-
66
- # Dummy GPU function for Hugging Face Spaces
67
- @spaces.GPU(duration=60)
68
- def dummy_gpu_function():
69
- """This function exists to satisfy Hugging Face Spaces GPU requirement"""
70
- import torch
71
- if torch.cuda.is_available():
72
- # Simple GPU operation
73
- x = torch.randn(1, 3, 224, 224).cuda()
74
- return x.cpu().numpy()
75
- return None
76
-
77
- # Initialize dummy GPU function on startup if on Spaces
78
- if SPACES_GPU_AVAILABLE:
79
- try:
80
- dummy_gpu_function()
81
- except:
82
- pass
83
-
84
- def add_watermark_to_video(input_video_path, output_video_path, watermark_text="Ginigen.com"):
85
- """Add watermark to video in the bottom-right corner"""
86
- try:
87
- # Open video
88
- cap = cv2.VideoCapture(input_video_path)
89
- fps = int(cap.get(cv2.CAP_PROP_FPS))
90
- width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
91
- height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
92
-
93
- # Video writer
94
- fourcc = cv2.VideoWriter_fourcc(*'mp4v')
95
- out = cv2.VideoWriter(output_video_path, fourcc, fps, (width, height))
96
-
97
- # Watermark settings
98
- font = cv2.FONT_HERSHEY_SIMPLEX
99
- font_scale = min(width, height) / 500 # Scale based on video size
100
- font_thickness = max(1, int(font_scale * 2))
101
-
102
- # Get text size
103
- (text_width, text_height), baseline = cv2.getTextSize(
104
- watermark_text, font, font_scale, font_thickness
105
- )
106
-
107
- # Position in bottom-right corner with padding
108
- padding = 20
109
- text_x = width - text_width - padding
110
- text_y = height - padding
111
-
112
- while True:
113
- ret, frame = cap.read()
114
- if not ret:
115
- break
116
-
117
- # Add semi-transparent background for better visibility
118
- overlay = frame.copy()
119
- cv2.rectangle(overlay,
120
- (text_x - 10, text_y - text_height - 10),
121
- (text_x + text_width + 10, text_y + 10),
122
- (0, 0, 0), -1)
123
- frame = cv2.addWeighted(frame, 0.7, overlay, 0.3, 0)
124
-
125
- # Add watermark text
126
- cv2.putText(frame, watermark_text, (text_x, text_y),
127
- font, font_scale, (255, 255, 255), font_thickness, cv2.LINE_AA)
128
-
129
- out.write(frame)
130
-
131
- cap.release()
132
- out.release()
133
- cv2.destroyAllWindows()
134
-
135
- return True
136
- except Exception as e:
137
- logging.error(f"Watermark error: {str(e)}")
138
- return False
139
-
140
- @spaces.GPU(duration=60)
141
- def generate_text_to_image(prompt, width, height, guidance, inference_steps, seed):
142
- if not prompt:
143
- return None, "Please enter a prompt"
144
-
145
- try:
146
- client = Client(TEXT2IMG_API_URL)
147
- if seed == -1:
148
- seed = random.randint(0, 9999999)
149
-
150
- result = client.predict(
151
- prompt=prompt,
152
- width=int(width),
153
- height=int(height),
154
- guidance=float(guidance),
155
- inference_steps=int(inference_steps),
156
- seed=int(seed),
157
- do_img2img=False,
158
- init_image=None,
159
- image2image_strength=0.8,
160
- resize_img=True,
161
- api_name="/generate_image"
162
- )
163
- return result[0], f"Seed used: {result[1]}"
164
- except Exception as e:
165
- logging.error(f"Image generation error: {str(e)}")
166
- return None, f"Error: {str(e)}"
167
 
168
- @spaces.GPU(duration=60)
169
- def generate_video_from_image(image, prompt="", length=4.0):
170
- if image is None:
171
- return None
172
-
173
- try:
174
- # 이미지 저장
175
- with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as fp:
176
- temp_path = fp.name
177
- Image.fromarray(image).save(temp_path)
178
-
179
- # API 호출
180
- client = Client(VIDEO_API_URL)
181
- result = client.predict(
182
- input_image=handle_file(temp_path),
183
- prompt=prompt if prompt else "Generate natural motion",
184
- n_prompt="",
185
- seed=random.randint(0, 9999999),
186
- use_teacache=True,
187
- video_length=float(length),
188
- api_name="/process"
189
- )
190
-
191
- os.unlink(temp_path)
192
-
193
- if result and len(result) > 0:
194
- video_dict = result[0]
195
- video_path = video_dict.get("video") if isinstance(video_dict, dict) else None
196
-
197
- if video_path:
198
- # Add watermark
199
- watermarked_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name
200
- if add_watermark_to_video(video_path, watermarked_path):
201
- return watermarked_path
202
- else:
203
- return video_path
204
-
205
- except Exception as e:
206
- logging.error(f"Video generation error: {str(e)}")
207
- return None
208
 
209
- # CSS with gradient background
210
- css = """
211
- :root {
212
- --primary-color: #f8c3cd;
213
- --secondary-color: #b3e5fc;
214
- --background-color: #f5f5f7;
215
- --card-background: #ffffff;
216
- --text-color: #424242;
217
- --accent-color: #ffb6c1;
218
- --success-color: #c8e6c9;
219
- --warning-color: #fff9c4;
220
- --shadow-color: rgba(0, 0, 0, 0.1);
221
- --border-radius: 12px;
222
  }
223
 
224
- body, .gradio-container {
225
- background: linear-gradient(135deg, #667eea 0%, #764ba2 25%, #f093fb 50%, #4facfe 75%, #00f2fe 100%) !important;
226
- background-size: 400% 400% !important;
227
- animation: gradientAnimation 15s ease infinite !important;
 
 
 
228
  }
229
 
230
- @keyframes gradientAnimation {
231
  0% {
232
- background-position: 0% 50%;
233
  }
234
- 50% {
235
- background-position: 100% 50%;
236
  }
237
  100% {
238
- background-position: 0% 50%;
239
  }
240
  }
241
 
242
- .gradio-container {
243
- max-width: 1200px !important;
244
- margin: 0 auto !important;
245
- padding: 20px !important;
246
- }
247
-
248
- .panel-box {
249
- border-radius: var(--border-radius) !important;
250
- box-shadow: 0 8px 16px var(--shadow-color) !important;
251
- background-color: var(--card-background) !important;
252
- padding: 20px !important;
253
- margin-bottom: 20px !important;
254
- backdrop-filter: blur(10px) !important;
255
- background-color: rgba(255, 255, 255, 0.95) !important;
256
- }
257
-
258
- #generate-btn, #video-btn {
259
- background: linear-gradient(135deg, #ff9a9e, #fad0c4) !important;
260
- font-size: 1.1rem !important;
261
- padding: 12px 24px !important;
262
- margin-top: 10px !important;
263
- width: 100% !important;
264
- border: none !important;
265
- color: white !important;
266
- font-weight: bold !important;
267
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2) !important;
268
- transition: all 0.3s ease !important;
269
- }
270
-
271
- #generate-btn:hover, #video-btn:hover {
272
- transform: translateY(-2px) !important;
273
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25) !important;
274
- }
275
-
276
- .main-content {
277
- min-height: 700px !important;
278
  }
279
 
280
- h1 {
281
- background: linear-gradient(135deg, #667eea, #764ba2) !important;
282
- -webkit-background-clip: text !important;
283
- -webkit-text-fill-color: transparent !important;
284
- text-align: center !important;
285
- font-size: 2.5rem !important;
286
- margin-bottom: 30px !important;
287
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1) !important;
288
- }
289
-
290
- .gr-form {
291
- background: rgba(255, 255, 255, 0.1) !important;
292
- border-radius: 8px !important;
293
- }
294
-
295
- .gr-box {
296
- border-radius: 8px !important;
297
- }
298
-
299
- .gr-padded {
300
- padding: 15px !important;
301
  }
302
  """
303
 
304
- # Gradio Interface
305
- demo = gr.Blocks(css=css, title="CINE VID Generation")
 
 
 
 
 
306
 
307
- with demo:
308
- gr.Markdown("# 🎨 FLUX VIDEO Generation")
 
 
 
 
 
 
 
309
 
310
- # Version update notice
 
 
 
 
 
 
 
 
 
 
 
 
311
  gr.HTML("""
312
- <div style='background: linear-gradient(135deg, #ff6b6b, #4ecdc4);
313
- padding: 20px;
314
- border-radius: 10px;
315
- margin-bottom: 20px;
316
- text-align: center;
317
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
318
- animation: pulse 2s infinite;'>
319
- <h3 style='color: white; margin-bottom: 10px; font-size: 1.5em;'>
320
- 🎉 Latest Version Update! 🎉
321
- </h3>
322
- <p style='color: white; margin-bottom: 15px; font-size: 1.1em;'>
323
- Please click the following link to access the latest version:
324
- </p>
325
- <a href='https://huggingface.co/spaces/ginigen/Flux-VIDEO'
326
- target='_blank'
327
- style='display: inline-block;
328
- padding: 12px 30px;
329
- background: white;
330
- color: #333;
331
- text-decoration: none;
332
- border-radius: 25px;
333
- font-weight: bold;
334
- font-size: 1.1em;
335
- transition: all 0.3s ease;
336
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);'>
337
- ✨ Go to Latest Version ✨
338
  </a>
339
  </div>
 
340
 
341
- <style>
342
- @keyframes pulse {
343
- 0% {
344
- box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7);
345
- }
346
- 70% {
347
- box-shadow: 0 0 0 20px rgba(255, 255, 255, 0);
348
- }
349
- 100% {
350
- box-shadow: 0 0 0 0 rgba(255, 255, 255, 0);
351
- }
352
- }
 
 
 
 
 
 
 
 
 
 
353
 
354
- div[style*="Latest Version Update"] a:hover {
355
- transform: translateY(-2px);
356
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
357
- background: #f0f0f0;
358
- }
359
- </style>
 
 
 
360
  """)
361
 
362
- # Single tab layout without tabs
363
- with gr.Row(equal_height=True, elem_classes="main-content"):
364
- # Input column
365
- with gr.Column(scale=1):
366
- with gr.Group(elem_classes="panel-box"):
367
- gr.Markdown("### 📝 Image Generation Settings")
368
-
369
- prompt = gr.Textbox(
370
- label="Prompt (Korean/English supported)",
371
- placeholder="Describe the image you want to generate...",
372
- lines=3
373
- )
374
-
375
- size_preset = gr.Dropdown(
376
- choices=list(IMAGE_PRESETS.keys()),
377
- value="1:1 Square",
378
- label="Size Preset"
379
- )
380
-
381
- with gr.Row():
382
- width = gr.Slider(256, 2048, 1024, step=64, label="Width")
383
- height = gr.Slider(256, 2048, 1024, step=64, label="Height")
384
-
385
- with gr.Row():
386
- guidance = gr.Slider(1.0, 20.0, 3.5, step=0.1, label="Guidance Scale")
387
- steps = gr.Slider(1, 50, 30, step=1, label="Steps")
388
-
389
- seed = gr.Number(label="Seed (-1=random)", value=-1)
390
-
391
- generate_btn = gr.Button("🎨 Generate Image", variant="primary", elem_id="generate-btn")
392
-
393
- with gr.Group(elem_classes="panel-box"):
394
- gr.Markdown("### 🎬 Video Generation Settings")
395
-
396
- video_prompt = gr.Textbox(
397
- label="(Optional) Video Prompt (Enter in English)",
398
- placeholder="Describe the motion for the video... (Leave empty for default motion)",
399
- lines=2
400
- )
401
-
402
- video_length = gr.Slider(
403
- minimum=1,
404
- maximum=4,
405
- value=2,
406
- step=0.5,
407
- label="Video Length (seconds)",
408
- info="Choose between 1 to 4 seconds(Full version supports up to 60 seconds)"
409
- )
410
-
411
- video_btn = gr.Button("🎬 Convert to Video", variant="secondary", elem_id="video-btn")
412
-
413
- # Output column
414
- with gr.Column(scale=1):
415
- with gr.Group(elem_classes="panel-box"):
416
- gr.Markdown("### 🖼️ Generation Results")
417
 
418
- output_image = gr.Image(label="Generated Image", type="numpy")
419
- output_seed = gr.Textbox(label="Seed Information")
420
- output_video = gr.Video(label="Generated Video")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
 
422
- # Event connections
423
- size_preset.change(update_dimensions, [size_preset], [width, height])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
 
425
- generate_btn.click(
426
- generate_text_to_image,
427
- [prompt, width, height, guidance, steps, seed],
428
- [output_image, output_seed]
429
  )
430
 
431
- video_btn.click(
432
- lambda img, v_prompt, length: generate_video_from_image(img, v_prompt, length) if img is not None else None,
433
- [output_image, video_prompt, video_length],
434
- [output_video]
435
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
 
437
  demo.launch()
 
1
  import gradio as gr
2
+ import time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ # CSS for styling
5
+ css = """
6
+ .link-box {
7
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
8
+ border-radius: 10px;
9
+ padding: 20px;
10
+ text-align: center;
11
+ color: white;
12
+ margin: 20px 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  }
14
 
15
+ .link-box a {
16
+ color: #ffd700;
17
+ font-weight: bold;
18
+ font-size: 1.2em;
19
+ text-decoration: none;
20
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ .link-box a:hover {
23
+ text-decoration: underline;
24
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
+ .popup-style {
27
+ background: #fff3cd;
28
+ border: 2px solid #ffc107;
29
+ border-radius: 8px;
30
+ padding: 15px;
31
+ margin: 10px 0;
 
 
 
 
 
 
 
32
  }
33
 
34
+ .important-link {
35
+ background: #d1ecf1;
36
+ border: 2px solid #17a2b8;
37
+ border-radius: 8px;
38
+ padding: 20px;
39
+ text-align: center;
40
+ animation: pulse 2s infinite;
41
  }
42
 
43
+ @keyframes pulse {
44
  0% {
45
+ box-shadow: 0 0 0 0 rgba(23, 162, 184, 0.7);
46
  }
47
+ 70% {
48
+ box-shadow: 0 0 0 10px rgba(23, 162, 184, 0);
49
  }
50
  100% {
51
+ box-shadow: 0 0 0 0 rgba(23, 162, 184, 0);
52
  }
53
  }
54
 
55
+ .modal-overlay {
56
+ position: fixed;
57
+ top: 0;
58
+ left: 0;
59
+ right: 0;
60
+ bottom: 0;
61
+ background: rgba(0, 0, 0, 0.7);
62
+ display: flex;
63
+ justify-content: center;
64
+ align-items: center;
65
+ z-index: 1000;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  }
67
 
68
+ .modal-content {
69
+ background: white;
70
+ padding: 30px;
71
+ border-radius: 10px;
72
+ max-width: 500px;
73
+ text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  }
75
  """
76
 
77
+ def show_link_notification():
78
+ """버튼 클릭 링크 안내 메시지를 표시"""
79
+ return gr.update(visible=True)
80
+
81
+ def hide_notification():
82
+ """알림 숨기기"""
83
+ return gr.update(visible=False)
84
 
85
+ # Gradio 앱 생성
86
+ with gr.Blocks(css=css, title="Link Guide Examples") as demo:
87
+ gr.Markdown("# 🔗 Gradio 링크 안내 예제")
88
+
89
+ # 방법 1: Markdown을 사용한 링크
90
+ gr.Markdown("""
91
+ ## 방법 1: Markdown 링크
92
+
93
+ 👉 **중요 공지**: [여기를 클릭하여 공식 웹사이트를 방문하세요](https://www.example.com)
94
 
95
+ **특별 이벤트**: [지금 바로 참여하기](https://www.example.com/event) - 선착순 100명!
96
+ """)
97
+
98
+ # 방법 2: HTML을 사용한 스타일링된 링크
99
+ gr.HTML("""
100
+ <div class="link-box">
101
+ <h3>🎁 특별 혜택 안내</h3>
102
+ <p>아래 링크를 클릭하여 특별 혜택을 받으세요!</p>
103
+ <a href="https://www.example.com" target="_blank">✨ 특별 혜택 받기 ✨</a>
104
+ </div>
105
+ """)
106
+
107
+ # 방법 3: 중요 알림 스타일
108
  gr.HTML("""
109
+ <div class="important-link">
110
+ <h3>⚠️ 필수 확인 사항</h3>
111
+ <p>서비스 이용을 위해 반드시 아래 링크를 확인해주세요:</p>
112
+ <a href="https://www.example.com/terms" target="_blank" style="font-size: 1.2em; color: #007bff;">
113
+ 📋 이용약관 확인하기
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  </a>
115
  </div>
116
+ """)
117
 
118
+ # 방법 4: 버튼 클릭 시 링크 안내
119
+ with gr.Row():
120
+ with gr.Column():
121
+ gr.Markdown("### 방법 4: 버튼 클릭으로 링크 안내")
122
+ show_link_btn = gr.Button("🔗 중요 링크 보기", variant="primary")
123
+
124
+ link_notification = gr.HTML(
125
+ """
126
+ <div class="popup-style">
127
+ <h4>📢 중요 안내</h4>
128
+ <p>아래 링크를 클릭하여 추가 정보를 확인하세요:</p>
129
+ <ul>
130
+ <li><a href="https://www.example.com/guide" target="_blank">📖 사용 가이드</a></li>
131
+ <li><a href="https://www.example.com/faq" target="_blank">❓ 자주 묻는 질문</a></li>
132
+ <li><a href="https://www.example.com/contact" target="_blank">📞 문의하기</a></li>
133
+ </ul>
134
+ </div>
135
+ """,
136
+ visible=False
137
+ )
138
+
139
+ close_btn = gr.Button("닫기", visible=False)
140
 
141
+ # 방법 5: 자동 팝업 스타일 (페이지 로드 시 표시)
142
+ gr.HTML("""
143
+ <script>
144
+ // 페이지 로드 후 3초 뒤에 알림 표시
145
+ setTimeout(function() {
146
+ // 실제로는 모달 창을 띄우거나 알림을 표시할 수 있습니다
147
+ console.log("링크 클릭 안내: https://www.example.com");
148
+ }, 3000);
149
+ </script>
150
  """)
151
 
152
+ # 방법 6: Info 메시지로 링크 안내
153
+ with gr.Row():
154
+ with gr.Column():
155
+ gr.Markdown("### 방법 5: 작업 완료 후 링크 안내")
156
+
157
+ def process_and_show_link(text):
158
+ # 어떤 처리를 수행
159
+ time.sleep(1) # 처리 시뮬레이션
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
+ # 처리 완료 링크 안내
162
+ result = f"처리 완료: {text}"
163
+ link_msg = """
164
+ <div style='background: #d4edda; border: 1px solid #c3e6cb;
165
+ border-radius: 5px; padding: 15px; margin-top: 10px;'>
166
+ <strong>✅ 작업이 완료되었습니다!</strong><br>
167
+ 결과를 확인하려면 <a href='https://www.example.com/results'
168
+ target='_blank' style='color: #155724; font-weight: bold;'>
169
+ 여기를 클릭</a>하세요.
170
+ </div>
171
+ """
172
+ return result, link_msg
173
+
174
+ input_text = gr.Textbox(label="텍스트 입력", placeholder="처리할 텍스트를 입력하세요")
175
+ process_btn = gr.Button("처리하기", variant="primary")
176
+ output_text = gr.Textbox(label="처리 결과")
177
+ link_message = gr.HTML()
178
 
179
+ # 방법 7: 조건부 링크 표시
180
+ with gr.Row():
181
+ with gr.Column():
182
+ gr.Markdown("### 방법 6: 조건부 링크 안내")
183
+
184
+ def check_and_show_link(agree):
185
+ if agree:
186
+ return """
187
+ <div style='background: #cfe2ff; border: 1px solid #6ea8fe;
188
+ border-radius: 5px; padding: 15px;'>
189
+ <strong>감사합니다!</strong><br>
190
+ 이제 <a href='https://www.example.com/start' target='_blank'
191
+ style='color: #084298; font-weight: bold;'>시작하기</a>
192
+ 링크를 클릭하여 진행하세요.
193
+ </div>
194
+ """
195
+ else:
196
+ return """
197
+ <div style='background: #f8d7da; border: 1px solid #f5c2c7;
198
+ border-radius: 5px; padding: 15px;'>
199
+ 동의하지 않으셨습니다. 서비스를 이용하려면 약관에 동의해주세요.
200
+ </div>
201
+ """
202
+
203
+ agree_checkbox = gr.Checkbox(label="이용약관에 동의합니다")
204
+ conditional_link = gr.HTML()
205
+
206
+ # 이벤트 연결
207
+ show_link_btn.click(
208
+ lambda: (gr.update(visible=True), gr.update(visible=True)),
209
+ outputs=[link_notification, close_btn]
210
+ )
211
 
212
+ close_btn.click(
213
+ lambda: (gr.update(visible=False), gr.update(visible=False)),
214
+ outputs=[link_notification, close_btn]
 
215
  )
216
 
217
+ process_btn.click(
218
+ process_and_show_link,
219
+ inputs=[input_text],
220
+ outputs=[output_text, link_message]
221
  )
222
+
223
+ agree_checkbox.change(
224
+ check_and_show_link,
225
+ inputs=[agree_checkbox],
226
+ outputs=[conditional_link]
227
+ )
228
+
229
+ # 추가 예제: 모달 스타일 팝업
230
+ gr.HTML("""
231
+ <div id="modal" style="display: none;" class="modal-overlay">
232
+ <div class="modal-content">
233
+ <h2>🎉 환영합니다!</h2>
234
+ <p>더 많은 기능을 사용하려면 아래 링크를 방문하세요:</p>
235
+ <a href="https://www.example.com/premium" target="_blank"
236
+ style="display: inline-block; padding: 10px 20px;
237
+ background: #007bff; color: white;
238
+ text-decoration: none; border-radius: 5px;">
239
+ 프리미엄 가입하기
240
+ </a>
241
+ <br><br>
242
+ <button onclick="document.getElementById('modal').style.display='none'"
243
+ style="padding: 5px 15px; cursor: pointer;">
244
+ 닫기
245
+ </button>
246
+ </div>
247
+ </div>
248
+
249
+ <script>
250
+ // 모달 표시 함수
251
+ function showModal() {
252
+ document.getElementById('modal').style.display = 'flex';
253
+ }
254
+
255
+ // 원하는 시점에 showModal() 호출
256
+ // 예: setTimeout(showModal, 5000); // 5초 후 표시
257
+ </script>
258
+ """)
259
 
260
  demo.launch()