Spaces:
Running
Running
import gradio as gr | |
import os | |
import tempfile | |
from datetime import datetime | |
from PIL import Image | |
# ํ๊ฒฝ๋ณ์์์ API ์๋ํฌ์ธํธ ๊ฐ์ ธ์ค๊ธฐ (์ฝ๋์์๋ ์ ๋ ๋ ธ์ถ ์๋จ) | |
API_ENDPOINT = os.getenv('API_ENDPOINT') | |
if not API_ENDPOINT: | |
print("โ API_ENDPOINT ํ๊ฒฝ๋ณ์๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค.") | |
exit(1) | |
# ๋ก๊ทธ ์ต์ ๋ ํด๋ผ์ด์ธํธ ์ํฌํธ | |
try: | |
from gradio_client import Client, handle_file | |
print("โ Gradio Client ์ํฌํธ ์ฑ๊ณต") | |
except ImportError as e: | |
print(f"โ Gradio Client ์ํฌํธ ์ค๋ฅ: {e}") | |
exit(1) | |
# ์ปค์คํ CSS ์คํ์ผ (๊ธฐ์กด๊ณผ ๋์ผ) | |
custom_css = """ | |
/* ============================================ | |
๋คํฌ๋ชจ๋ ์๋ ๋ณ๊ฒฝ ํ ํ๋ฆฟ CSS | |
============================================ */ | |
/* 1. CSS ๋ณ์ ์ ์ (๋ผ์ดํธ๋ชจ๋ - ๊ธฐ๋ณธ๊ฐ) */ | |
:root { | |
/* ๋ฉ์ธ ์ปฌ๋ฌ */ | |
--primary-color: #FB7F0D; | |
--secondary-color: #ff9a8b; | |
--accent-color: #FF6B6B; | |
/* ๋ฐฐ๊ฒฝ ์ปฌ๋ฌ */ | |
--background-color: #FFFFFF; | |
--card-bg: #ffffff; | |
--input-bg: #ffffff; | |
/* ํ ์คํธ ์ปฌ๋ฌ */ | |
--text-color: #334155; | |
--text-secondary: #64748b; | |
/* ๋ณด๋ ๋ฐ ๊ตฌ๋ถ์ */ | |
--border-color: #dddddd; | |
--border-light: #e5e5e5; | |
/* ํ ์ด๋ธ ์ปฌ๋ฌ */ | |
--table-even-bg: #f3f3f3; | |
--table-hover-bg: #f0f0f0; | |
/* ๊ทธ๋ฆผ์ */ | |
--shadow: 0 8px 30px rgba(251, 127, 13, 0.08); | |
--shadow-light: 0 2px 4px rgba(0, 0, 0, 0.1); | |
/* ๊ธฐํ */ | |
--border-radius: 18px; | |
} | |
/* 2. ๋คํฌ๋ชจ๋ ์์ ๋ณ์ (์๋ ๊ฐ์ง) */ | |
@media (prefers-color-scheme: dark) { | |
:root { | |
/* ๋ฐฐ๊ฒฝ ์ปฌ๋ฌ */ | |
--background-color: #1a1a1a; | |
--card-bg: #2d2d2d; | |
--input-bg: #2d2d2d; | |
/* ํ ์คํธ ์ปฌ๋ฌ */ | |
--text-color: #e5e5e5; | |
--text-secondary: #a1a1aa; | |
/* ๋ณด๋ ๋ฐ ๊ตฌ๋ถ์ */ | |
--border-color: #404040; | |
--border-light: #525252; | |
/* ํ ์ด๋ธ ์ปฌ๋ฌ */ | |
--table-even-bg: #333333; | |
--table-hover-bg: #404040; | |
/* ๊ทธ๋ฆผ์ */ | |
--shadow: 0 8px 30px rgba(0, 0, 0, 0.3); | |
--shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2); | |
} | |
} | |
/* 3. ์๋ ๋คํฌ๋ชจ๋ ํด๋์ค (Gradio ํ ๊ธ์ฉ) */ | |
[data-theme="dark"], | |
.dark, | |
.gr-theme-dark { | |
/* ๋ฐฐ๊ฒฝ ์ปฌ๋ฌ */ | |
--background-color: #1a1a1a; | |
--card-bg: #2d2d2d; | |
--input-bg: #2d2d2d; | |
/* ํ ์คํธ ์ปฌ๋ฌ */ | |
--text-color: #e5e5e5; | |
--text-secondary: #a1a1aa; | |
/* ๋ณด๋ ๋ฐ ๊ตฌ๋ถ์ */ | |
--border-color: #404040; | |
--border-light: #525252; | |
/* ํ ์ด๋ธ ์ปฌ๋ฌ */ | |
--table-even-bg: #333333; | |
--table-hover-bg: #404040; | |
/* ๊ทธ๋ฆผ์ */ | |
--shadow: 0 8px 30px rgba(0, 0, 0, 0.3); | |
--shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2); | |
} | |
/* 4. ๊ธฐ๋ณธ ์์ ๋คํฌ๋ชจ๋ ์ ์ฉ */ | |
body { | |
font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif; | |
background-color: var(--background-color) !important; | |
color: var(--text-color) !important; | |
line-height: 1.6; | |
margin: 0; | |
padding: 0; | |
font-size: 16px; | |
transition: background-color 0.3s ease, color 0.3s ease; | |
} | |
/* 5. Gradio ์ปจํ ์ด๋ ๊ฐ์ ์ ์ฉ */ | |
.gradio-container, | |
.gradio-container *, | |
.gr-app, | |
.gr-app *, | |
.gr-interface { | |
background-color: var(--background-color) !important; | |
color: var(--text-color) !important; | |
} | |
/* ํธํฐ ์จ๊น ์ค์ ์ถ๊ฐ */ | |
footer { | |
visibility: hidden; | |
} | |
.gradio-container { | |
width: 100%; | |
margin: 0 auto; | |
padding: 20px; | |
background-color: var(--background-color); | |
} | |
/* โโ ๊ทธ๋ฃน ๋ํผ ๋ฐฐ๊ฒฝ ์์ ์ ๊ฑฐ โโ */ | |
.custom-section-group, | |
.gr-block.gr-group { | |
background-color: var(--background-color) !important; | |
box-shadow: none !important; | |
} | |
.custom-section-group::before, | |
.custom-section-group::after, | |
.gr-block.gr-group::before, | |
.gr-block.gr-group::after { | |
display: none !important; | |
content: none !important; | |
} | |
/* ๊ทธ๋ฃน ์ปจํ ์ด๋ ๋ฐฐ๊ฒฝ์ ์์ด๋ณด๋ฆฌ๋ก, ๊ทธ๋ฆผ์ ์ ๊ฑฐ */ | |
.custom-section-group { | |
background-color: var(--background-color) !important; | |
box-shadow: none !important; | |
} | |
/* 6. ์นด๋ ๋ฐ ํจ๋ ์คํ์ผ */ | |
.custom-frame, | |
.gr-form, | |
.gr-box, | |
.gr-panel, | |
[class*="frame"], | |
[class*="card"], | |
[class*="panel"] { | |
background-color: var(--card-bg) !important; | |
border: 1px solid var(--border-color) !important; | |
border-radius: var(--border-radius); | |
padding: 20px; | |
margin: 10px 0; | |
box-shadow: var(--shadow) !important; | |
color: var(--text-color) !important; | |
} | |
/* ์น์ ๊ทธ๋ฃน ์คํ์ผ - ํ์ ๋ฐฐ๊ฒฝ ์์ ์ ๊ฑฐ */ | |
.custom-section-group { | |
margin-top: 20px; | |
padding: 0; | |
border: none; | |
border-radius: 0; | |
background-color: var(--background-color); | |
box-shadow: none !important; | |
} | |
/* ๋ฒํผ ์คํ์ผ - ๊ธ์ ํฌ๊ธฐ 18px */ | |
.custom-button { | |
border-radius: 30px !important; | |
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important; | |
color: white !important; | |
font-size: 18px !important; | |
padding: 10px 20px !important; | |
border: none; | |
box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25); | |
transition: transform 0.3s ease; | |
} | |
.custom-button:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3); | |
} | |
/* ์ ๋ชฉ ์คํ์ผ (๋ชจ๋ ํญ๋ชฉ๋ช ์ด ๋์ผํ๊ฒ custom-title ํด๋์ค๋ก) */ | |
.custom-title { | |
font-size: 28px; | |
font-weight: bold; | |
margin-bottom: 10px; | |
color: var(--text-color); | |
border-bottom: 2px solid var(--primary-color); | |
padding-bottom: 5px; | |
} | |
/* ์ด๋ฏธ์ง ์ปจํ ์ด๋ - ํฌ๊ธฐ ๊ณ ์ */ | |
.image-container { | |
border-radius: var(--border-radius); | |
overflow: hidden; | |
border: 2px dashed var(--border-color); | |
transition: all 0.3s ease; | |
background-color: var(--card-bg); | |
} | |
/* ์ด๋ฏธ์ง ์ ๋ก๋ ์์ญ ๊ฐ์ */ | |
.gradio-container .gr-image { | |
border: 2px dashed var(--border-color) !important; | |
border-radius: var(--border-radius) !important; | |
background-color: var(--card-bg) !important; | |
transition: all 0.3s ease !important; | |
} | |
.gradio-container .gr-image:hover { | |
border-color: var(--primary-color) !important; | |
box-shadow: 0 4px 12px rgba(251, 127, 13, 0.15) !important; | |
} | |
/* ์ ๋ก๋ ์์ญ ๋ด๋ถ ํ ์คํธ */ | |
.gradio-container .gr-image .upload-container, | |
.gradio-container .gr-image [data-testid="upload-container"] { | |
background-color: var(--card-bg) !important; | |
color: var(--text-color) !important; | |
border: none !important; | |
} | |
/* ์ ๋ก๋ ์์ญ ๋๋๊ทธ ์๋ด ํ ์คํธ */ | |
.gradio-container .gr-image .upload-container p, | |
.gradio-container .gr-image [data-testid="upload-container"] p { | |
color: var(--text-color) !important; | |
font-size: 14px !important; | |
} | |
/* ์ ๋ก๋ ๋ฒํผ ์คํ์ผ ๊ฐ์ */ | |
.gradio-container .gr-image .upload-container button, | |
.gradio-container .gr-image [data-testid="upload-container"] button { | |
background-color: var(--primary-color) !important; | |
color: white !important; | |
border: none !important; | |
padding: 8px 16px !important; | |
border-radius: 8px !important; | |
font-size: 14px !important; | |
cursor: pointer !important; | |
transition: all 0.3s ease !important; | |
} | |
.gradio-container .gr-image .upload-container button:hover, | |
.gradio-container .gr-image [data-testid="upload-container"] button:hover { | |
background-color: var(--secondary-color) !important; | |
transform: translateY(-1px) !important; | |
} | |
/* ์ ๋ก๋ ์์ญ ์์ด์ฝ */ | |
.gradio-container .gr-image .upload-container svg, | |
.gradio-container .gr-image [data-testid="upload-container"] svg { | |
color: var(--primary-color) !important; | |
width: 32px !important; | |
height: 32px !important; | |
} | |
/* ์ด๋ฏธ์ง๊ฐ ์ ๋ก๋๋ ํ ํ์ ์์ญ */ | |
.gradio-container .gr-image img { | |
background-color: var(--card-bg) !important; | |
border-radius: var(--border-radius) !important; | |
} | |
/* ์ด๋ฏธ์ง ์ ๊ฑฐ ๋ฒํผ */ | |
.gradio-container .gr-image .image-container button, | |
.gradio-container .gr-image [data-testid="image"] button { | |
background-color: rgba(255, 255, 255, 0.9) !important; | |
color: #333 !important; | |
border: none !important; | |
border-radius: 50% !important; | |
width: 28px !important; | |
height: 28px !important; | |
display: flex !important; | |
align-items: center !important; | |
justify-content: center !important; | |
cursor: pointer !important; | |
transition: all 0.3s ease !important; | |
} | |
.gradio-container .gr-image .image-container button:hover, | |
.gradio-container .gr-image [data-testid="image"] button:hover { | |
background-color: rgba(255, 255, 255, 1) !important; | |
transform: scale(1.1) !important; | |
} | |
/* ์ ๋ก๋ ์ด๋ฏธ์ง ์ปจํ ์ด๋ (600x600) */ | |
.upload-image-container { | |
width: 600px !important; | |
height: 600px !important; | |
min-width: 600px !important; | |
min-height: 600px !important; | |
max-width: 600px !important; | |
max-height: 600px !important; | |
} | |
/* ์ถ๋ ฅ ์ด๋ฏธ์ง ์ปจํ ์ด๋ (700x600) */ | |
.output-image-container { | |
width: 700px !important; | |
height: 600px !important; | |
min-width: 700px !important; | |
min-height: 600px !important; | |
max-width: 700px !important; | |
max-height: 600px !important; | |
} | |
.image-container:hover { | |
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); | |
} | |
.image-container img { | |
width: 100% !important; | |
height: 100% !important; | |
object-fit: contain !important; | |
} | |
/* Gradio ์ ๋ก๋ ์ด๋ฏธ์ง ์ปดํฌ๋ํธ ํฌ๊ธฐ ๊ณ ์ (600x600) */ | |
.gradio-container .gr-image.upload-image { | |
width: 600px !important; | |
height: 600px !important; | |
min-width: 600px !important; | |
min-height: 600px !important; | |
max-width: 600px !important; | |
max-height: 600px !important; | |
} | |
/* Gradio ์ถ๋ ฅ ์ด๋ฏธ์ง ์ปดํฌ๋ํธ ํฌ๊ธฐ ๊ณ ์ (700x600) */ | |
.gradio-container .gr-image.output-image { | |
width: 700px !important; | |
height: 600px !important; | |
min-width: 700px !important; | |
min-height: 600px !important; | |
max-width: 700px !important; | |
max-height: 600px !important; | |
} | |
/* ์ด๋ฏธ์ง ์ ๋ก๋ ์์ญ ํฌ๊ธฐ ๊ณ ์ */ | |
.gradio-container .gr-image.upload-image > div { | |
width: 600px !important; | |
height: 600px !important; | |
min-width: 600px !important; | |
min-height: 600px !important; | |
max-width: 600px !important; | |
max-height: 600px !important; | |
} | |
/* ์ด๋ฏธ์ง ์ถ๋ ฅ ์์ญ ํฌ๊ธฐ ๊ณ ์ */ | |
.gradio-container .gr-image.output-image > div { | |
width: 700px !important; | |
height: 600px !important; | |
min-width: 700px !important; | |
min-height: 600px !important; | |
max-width: 700px !important; | |
max-height: 600px !important; | |
} | |
/* ์ด๋ฏธ์ง ์ ๋ก๋ ๋๋๊ทธ ์์ญ ํฌ๊ธฐ ๊ณ ์ */ | |
.gradio-container .gr-image.upload-image .image-container, | |
.gradio-container .gr-image.upload-image [data-testid="image"], | |
.gradio-container .gr-image.upload-image .upload-container { | |
width: 600px !important; | |
height: 600px !important; | |
min-width: 600px !important; | |
min-height: 600px !important; | |
max-width: 600px !important; | |
max-height: 600px !important; | |
} | |
/* ์ด๋ฏธ์ง ์ถ๋ ฅ ๋๋๊ทธ ์์ญ ํฌ๊ธฐ ๊ณ ์ */ | |
.gradio-container .gr-image.output-image .image-container, | |
.gradio-container .gr-image.output-image [data-testid="image"], | |
.gradio-container .gr-image.output-image .upload-container { | |
width: 700px !important; | |
height: 600px !important; | |
min-width: 700px !important; | |
min-height: 600px !important; | |
max-width: 700px !important; | |
max-height: 600px !important; | |
} | |
/* 7. ์ ๋ ฅ ํ๋ ์คํ์ผ */ | |
.gr-input, .gr-text-input, .gr-sample-inputs, | |
input[type="text"], | |
input[type="number"], | |
input[type="email"], | |
input[type="password"], | |
textarea, | |
select, | |
.gr-textarea, | |
.gr-dropdown { | |
border-radius: var(--border-radius) !important; | |
border: 1px solid var(--border-color) !important; | |
padding: 14px !important; | |
font-size: 15px !important; | |
background-color: var(--input-bg) !important; | |
color: var(--text-color) !important; | |
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important; | |
transition: all 0.3s ease !important; | |
} | |
.gr-input:focus, .gr-text-input:focus, | |
input[type="text"]:focus, | |
input[type="number"]:focus, | |
input[type="email"]:focus, | |
input[type="password"]:focus, | |
textarea:focus, | |
select:focus, | |
.gr-textarea:focus, | |
.gr-dropdown:focus { | |
border-color: var(--primary-color) !important; | |
outline: none !important; | |
box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important; | |
} | |
/* 8. ๋ผ๋ฒจ ๋ฐ ํ ์คํธ ์์ */ | |
.gradio-container label, | |
label, | |
.gr-label, | |
.gr-checkbox label, | |
.gr-radio label, | |
p, span, div { | |
color: var(--text-color) !important; | |
font-size: 16px !important; | |
font-weight: 600 !important; | |
margin-bottom: 8px !important; | |
} | |
/* ๋๋กญ๋ค์ด ๋ฐ ๋ผ๋์ค ๋ฒํผ ํฐํธ ํฌ๊ธฐ */ | |
.gr-radio label, .gr-dropdown label, .gr-checkbox label { | |
font-size: 15px !important; | |
} | |
/* ๋ผ๋์ค ๋ฒํผ ์ ํ์ง ๋ณผ๋ ์ฒ๋ฆฌ ์ ๊ฑฐ */ | |
.gr-radio .gr-radio-option label, | |
.gr-radio input[type="radio"] + label, | |
.gr-radio .gr-form label { | |
font-weight: normal !important; | |
font-size: 15px !important; | |
} | |
/* ๋ผ๋์ค ๋ฒํผ ๊ทธ๋ฃน ๋ด ๋ชจ๋ ๋ผ๋ฒจ ์ผ๋ฐ ํฐํธ๋ก ์ค์ */ | |
.gr-radio fieldset label { | |
font-weight: normal !important; | |
} | |
/* ๋งํฌ๋ค์ด ํ ์คํธ ํฌ๊ธฐ ์ฆ๊ฐ */ | |
.gradio-container .gr-markdown { | |
font-size: 15px !important; | |
line-height: 1.6 !important; | |
color: var(--text-color) !important; | |
} | |
/* ํ ์คํธ๋ฐ์ค ๋ด์ฉ ํฐํธ ํฌ๊ธฐ */ | |
.gr-textbox textarea, .gr-textbox input { | |
font-size: 15px !important; | |
background-color: var(--input-bg) !important; | |
color: var(--text-color) !important; | |
} | |
/* ์์ฝ๋์ธ ์ ๋ชฉ ํฐํธ ํฌ๊ธฐ */ | |
.gr-accordion summary { | |
font-size: 17px !important; | |
font-weight: 600 !important; | |
background-color: var(--card-bg) !important; | |
color: var(--text-color) !important; | |
} | |
/* ๋ฉ์ธ ์ปจํ ์ธ ์คํฌ๋กค๋ฐ */ | |
::-webkit-scrollbar { | |
width: 8px; | |
height: 8px; | |
} | |
::-webkit-scrollbar-track { | |
background: var(--card-bg); | |
border-radius: 10px; | |
} | |
::-webkit-scrollbar-thumb { | |
background: var(--primary-color); | |
border-radius: 10px; | |
} | |
::-webkit-scrollbar-thumb:hover { | |
background: var(--secondary-color); | |
} | |
/* ์ ๋๋ฉ์ด์ ์คํ์ผ */ | |
@keyframes fadeIn { | |
from { opacity: 0; transform: translateY(10px); } | |
to { opacity: 1; transform: translateY(0); } | |
} | |
.fade-in { | |
animation: fadeIn 0.5s ease-out; | |
} | |
/* ๋ฐ์ํ */ | |
@media (max-width: 768px) { | |
.button-grid { | |
grid-template-columns: repeat(2, 1fr); | |
} | |
} | |
/* ์น์ ์ ๋ชฉ ์คํ์ผ */ | |
.section-title { | |
display: flex; | |
align-items: center; | |
font-size: 24px; | |
font-weight: 700; | |
color: var(--text-color); | |
margin-bottom: 15px; | |
padding-bottom: 8px; | |
border-bottom: 2px solid var(--primary-color); | |
font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif; | |
} | |
.section-title img { | |
margin-right: 12px; | |
width: 28px; | |
height: 28px; | |
/* ๋คํฌ๋ชจ๋์์ ์์ด์ฝ ํํฐ ์ ์ฉ */ | |
filter: brightness(0) saturate(100%) invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg) brightness(104%) contrast(97%); | |
} | |
/* ๋ผ์ดํธ๋ชจ๋์์๋ ์๋ ์์ด์ฝ ์์ ์ ์ง */ | |
@media (prefers-color-scheme: light) { | |
.section-title img { | |
filter: none; | |
} | |
} | |
/* ์๋ ๋คํฌ๋ชจ๋ ํด๋์ค์์๋ ์์ด์ฝ ์์ ์ ์ฉ */ | |
[data-theme="dark"] .section-title img, | |
.dark .section-title img, | |
.gr-theme-dark .section-title img { | |
filter: brightness(0) saturate(100%) invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg) brightness(104%) contrast(97%); | |
} | |
/* 10. ์์ฝ๋์ธ ๋ฐ ๋๋กญ๋ค์ด - ์๋์ค์ ์์ญ ํ์ ๋ฐฐ๊ฒฝ ์ ๊ฑฐ */ | |
details, | |
.gr-accordion, | |
.gr-accordion details { | |
background-color: var(--background-color) !important; | |
border: 1px solid var(--border-color) !important; | |
color: var(--text-color) !important; | |
border-radius: var(--border-radius) !important; | |
margin: 10px 0 !important; | |
} | |
details summary, | |
.gr-accordion summary, | |
.gr-accordion details summary { | |
background-color: var(--card-bg) !important; | |
color: var(--text-color) !important; | |
padding: 12px 16px !important; | |
border-radius: var(--border-radius) !important; | |
cursor: pointer !important; | |
border: none !important; | |
font-weight: 600 !important; | |
transition: all 0.3s ease !important; | |
} | |
details summary:hover, | |
.gr-accordion summary:hover, | |
.gr-accordion details summary:hover { | |
background-color: var(--table-hover-bg) !important; | |
} | |
/* ์์ฝ๋์ธ ๋ด๋ถ ์ฝํ ์ธ */ | |
details[open], | |
.gr-accordion[open], | |
.gr-accordion details[open] { | |
background-color: var(--background-color) !important; | |
} | |
details[open] > *:not(summary), | |
.gr-accordion[open] > *:not(summary), | |
.gr-accordion details[open] > *:not(summary) { | |
background-color: var(--background-color) !important; | |
color: var(--text-color) !important; | |
padding: 16px !important; | |
border-top: 1px solid var(--border-color) !important; | |
} | |
/* ๊ทธ๋ฃน ๋ด๋ถ ์คํ์ผ - ์๋์ค์ ๋ด๋ถ ๊ทธ๋ฃน๋ค */ | |
.gr-group, | |
details .gr-group, | |
.gr-accordion .gr-group { | |
background-color: var(--background-color) !important; | |
border: none !important; | |
padding: 12px 0 !important; | |
margin: 8px 0 !important; | |
border-radius: var(--border-radius) !important; | |
} | |
/* ๊ทธ๋ฃน ๋ด๋ถ ์ ๋ชฉ */ | |
.gr-group .gr-markdown h3, | |
.gr-group h3 { | |
color: var(--text-color) !important; | |
font-size: 16px !important; | |
font-weight: 600 !important; | |
margin-bottom: 12px !important; | |
padding-bottom: 6px !important; | |
border-bottom: 1px solid var(--border-color) !important; | |
} | |
/* 11. ์ถ๊ฐ Gradio ์ปดํฌ๋ํธ๋ค */ | |
.gr-block, | |
.gr-group, | |
.gr-row, | |
.gr-column { | |
background-color: var(--background-color) !important; | |
color: var(--text-color) !important; | |
} | |
/* 12. ๋ฒํผ์ ๊ธฐ์กด ์คํ์ผ ์ ์ง (primary-color ์ฌ์ฉ) */ | |
button:not([class*="custom"]):not([class*="primary"]):not([class*="secondary"]) { | |
background-color: var(--card-bg) !important; | |
color: var(--text-color) !important; | |
border-color: var(--border-color) !important; | |
} | |
/* 13. ์ฝ๋ ๋ธ๋ก ๋ฐ pre ํ๊ทธ */ | |
code, | |
pre, | |
.code-block { | |
background-color: var(--table-even-bg) !important; | |
color: var(--text-color) !important; | |
border-color: var(--border-color) !important; | |
} | |
/* 14. ์ ํ ์ ๋๋ฉ์ด์ */ | |
* { | |
transition: background-color 0.3s ease, | |
color 0.3s ease, | |
border-color 0.3s ease !important; | |
} | |
""" | |
# FontAwesome ์์ด์ฝ ํฌํจ | |
fontawesome_link = """ | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" /> | |
""" | |
def create_download_filename(keyword): | |
"""ํ๊ตญ์๊ฐ ๊ธฐ์ค์ผ๋ก ๋ค์ด๋ก๋ ํ์ผ๋ช ์์ฑ""" | |
from datetime import datetime, timezone, timedelta | |
# ํ๊ตญ ์๊ฐ๋ ์ค์ (UTC+9) | |
kst = timezone(timedelta(hours=9)) | |
now = datetime.now(kst) | |
# ํ์ผ๋ช ์ ์ฌ์ฉํ ์ ์๋ ๋ฌธ์ ์ ๊ฑฐ | |
import re | |
safe_keyword = re.sub(r'[<>:"/\\|?*]', '_', keyword) if keyword else "์ํ" | |
safe_keyword = safe_keyword[:30] # ๊ธธ์ด ์ ํ 30์๋ก ๋ณ๊ฒฝ | |
# YYMMDD_์๊ฐ๋ถ ํ์ | |
time_str = now.strftime("%y%m%d_%H%M") | |
filename = f"{safe_keyword}_{time_str}.jpg" | |
return filename | |
def prepare_download_file(image, keyword): | |
"""๋ค์ด๋ก๋์ฉ ์์ ํ์ผ ์์ฑ (์ฌ๋ฐ๋ฅธ ํ์ผ๋ช ํฌํจ)""" | |
if image is None: | |
return None | |
try: | |
# ํ๊ตญ์๊ฐ ๊ธฐ์ค ํ์ผ๋ช ์์ฑ | |
filename = create_download_filename(keyword) | |
# ์์ ๋๋ ํ ๋ฆฌ์ ์ํ๋ ํ์ผ๋ช ์ผ๋ก ์ ์ฅ | |
import tempfile | |
import os | |
# ์์ ๋๋ ํ ๋ฆฌ ์์ฑ | |
temp_dir = tempfile.mkdtemp() | |
file_path = os.path.join(temp_dir, filename) | |
# PIL Image๋ฅผ RGB๋ก ๋ณํ ํ ์ ์ฅ | |
if isinstance(image, Image.Image): | |
# RGBA๋ฅผ RGB๋ก ๋ณํ | |
if image.mode == 'RGBA': | |
background = Image.new('RGB', image.size, (255, 255, 255)) | |
background.paste(image, mask=image.split()[-1]) | |
image_to_save = background | |
else: | |
image_to_save = image.convert('RGB') | |
else: | |
image_to_save = image | |
# JPG๋ก ์ ์ฅ (์ํ๋ ํ์ผ๋ช ์ผ๋ก) | |
image_to_save.save(file_path, 'JPEG', quality=95) | |
print(f"โ ๋ค์ด๋ก๋ ํ์ผ ์ค๋น: {filename}") | |
return file_path | |
except Exception as e: | |
print(f"โ ๋ค์ด๋ก๋ ํ์ผ ์ค๋น ์คํจ: {e}") | |
return None | |
# ๐ฏ ํต์ฌ: ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ ํจ์ (๋ก๊ทธ ์ต์ํ) | |
def get_client(): | |
"""ํ๊ฒฝ๋ณ์ ๊ธฐ๋ฐ ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ""" | |
try: | |
client = Client(API_ENDPOINT) | |
return client | |
except Exception as e: | |
print(f"โ ์ฐ๊ฒฐ ์คํจ: {str(e)[:50]}...") | |
return None | |
def main(): | |
with gr.Blocks( | |
css=custom_css, | |
theme=gr.themes.Default( | |
primary_hue="orange", | |
secondary_hue="orange", | |
font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"] | |
), | |
title="UHP ์ด๋ฏธ์ง์์ฑ๊ธฐ" | |
) as app: | |
# FontAwesome ๋งํฌ ์ถ๊ฐ | |
gr.HTML(fontawesome_link) | |
# ์ํ ๊ด๋ฆฌ | |
copy_suggestions_state = gr.State({}) | |
current_keyword_state = gr.State("") | |
with gr.Row(): | |
# ์ผ์ชฝ: 1๋จ๊ณ AI ์นดํผ ์์ฑ | |
with gr.Column(scale=1): | |
# AI ์นดํผ ์์ฑ ์น์ | |
with gr.Column(elem_classes="custom-frame"): | |
gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/3097/3097412.png"> 1๋จ๊ณ: AI ์นดํผ ์์ฑ</div>') | |
product_keyword = gr.Textbox( | |
label="์ํ ํค์๋", | |
placeholder="์: ๋ฐ๋๋ฐ์ ์ถ์ถ๋ฌผ, ํ๋ฆฌ๋ฏธ์ ํ ๋ธ๋ฌ, ์ฒ์ฐ ์คํจ์ผ์ด" | |
) | |
copy_type_selection = gr.Radio( | |
choices=[ | |
"์ฅ์ ์์ฝํ", "๋ฌธ์ ์ ์ํ", "์ฌํ์ ์ฆ๊ฑฐํ", "๊ธด๊ธ์ฑ์ ๋ํ", | |
"๊ฐ๊ฒฉ๊ฒฝ์๋ ฅํ", "๋งค์ธ๋ณํํ", "์ถฉ๋๊ตฌ๋งค์ ๋ํ", "๊ณตํฌ์๊ตฌํ" | |
], | |
label="๐ 1. ์นดํผ ํ์ ์ ํ", | |
value="์ฅ์ ์์ฝํ", | |
visible=True | |
) | |
copy_type_description = gr.Markdown( | |
"**์ฅ์ ์์ฝํ**: ์ ํ์ ์ฅ์ ์ ํ๋์ ๊ฐ์กฐ - ํต์ฌ ๊ธฐ๋ฅ๊ณผ ํํ์ ๊ฐ๊ฒฐํ๊ฒ ์์ฝํ์ฌ ์ ์", | |
visible=True | |
) | |
generate_copy_btn = gr.Button("๋ฉ์ธ์นดํผ ์์ฑ", elem_classes="custom-button") | |
# ์นดํผ ์ถ๋ ฅ ์์ญ | |
copy1_display = gr.Textbox(label="์ถ์ฒ1", interactive=False, show_label=True) | |
copy2_display = gr.Textbox(label="์ถ์ฒ2", interactive=False, show_label=True) | |
copy3_display = gr.Textbox(label="์ถ์ฒ3", interactive=False, show_label=True) | |
copy4_display = gr.Textbox(label="์ถ์ฒ4", interactive=False, show_label=True) | |
copy5_display = gr.Textbox(label="์ถ์ฒ5", interactive=False, show_label=True) | |
copy_selection = gr.Radio( | |
choices=["์ถ์ฒ 1", "์ถ์ฒ 2", "์ถ์ฒ 3", "์ถ์ฒ 4", "์ถ์ฒ 5"], | |
label="๐ 2. ์นดํผ ์ ํ", | |
value="์ถ์ฒ 1", | |
visible=True | |
) | |
with gr.Row(): | |
main_text = gr.Textbox( | |
label="๋ฉ์ธ์นดํผ", | |
placeholder="์์์ ์นดํผ๋ฅผ ์ ํํ๋ฉด ์๋์ผ๋ก ์ ๋ ฅ๋ฉ๋๋ค", | |
interactive=True | |
) | |
sub_text = gr.Textbox( | |
label="์๋ธ์นดํผ", | |
placeholder="์์์ ์นดํผ๋ฅผ ์ ํํ๋ฉด ์๋์ผ๋ก ์ ๋ ฅ๋ฉ๋๋ค", | |
interactive=True | |
) | |
# ์์ | |
gr.Examples( | |
examples=[ | |
["ํต๊ตฝ์ฌ๋ฆฌํผ"], ["๋ฐ๋๋ฐ์ ์ถ์ถ๋ฌผ"], ["ํ๋ฆฌ๋ฏธ์ ํ ๋ธ๋ฌ"], | |
["์ฒ์ฐ ์คํจ์ผ์ด"], ["์ ๊ธฐ๋ ์๋"], ["๋ฌด์ ์ด์ดํฐ"] | |
], | |
inputs=[product_keyword] | |
) | |
# ์ค๋ฅธ์ชฝ: 2๋จ๊ณ ์ด๋ฏธ์ง์์ฑ | |
with gr.Column(scale=1): | |
# ์ด๋ฏธ์ง ์ ๋ก๋ ๋ฐ ์ค์ ์น์ | |
with gr.Column(elem_classes="custom-frame"): | |
gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/4297/4297825.png"> 2๋จ๊ณ: ์ด๋ฏธ์ง์์ฑ</div>') | |
input_image = gr.Image( | |
type="filepath", | |
label="์ํ ์ด๋ฏธ์ง ์ ๋ก๋", | |
elem_classes="image-container upload-image-container" | |
) | |
color_mode = gr.Radio( | |
choices=["์ถ์ฒ๋ฐฐ๊ฒฝ", "ํฐ์๋ฐฐ๊ฒฝ", "์๋ ์ค์ ๋ฐฐ๊ฒฝ"], | |
value="์ถ์ฒ๋ฐฐ๊ฒฝ", | |
label="๋ฐฐ๊ฒฝ์ ์ค์ " | |
) | |
generate_image_btn = gr.Button("์ด๋ฏธ์ง์์ฑ", elem_classes="custom-button") | |
# ์๋์ค์ ์์ฝ๋์ธ | |
with gr.Accordion("์๋์ค์ ", open=False): | |
with gr.Group(): | |
gr.Markdown("### ํฐํธ ์ ํ") | |
with gr.Row(): | |
main_font_choice = gr.Dropdown( | |
choices=[ | |
"ํ๋ฆฌํ ๋ค๋-Bold", "์์ค์ฝ์ด๋๋ฆผ-Bold", "๋ ธํ ์ฐ์ค-Bold", | |
"๋ฐฐ๋ฌ์๋ฏผ์กฑ ๋ํ์ฒด", "๋ฐฐ๋ฌ์๋ฏผ์กฑ ์ฃผ์์ฒด", "์ฌ๊ธฐ์ด๋ ์๋์ฒด", | |
"์จ๊ธ์ ์ฝ์ฝ์ฒด", "์จ๊ธ์ ๋ฐ๋คํ์ฒด", "์ด์ฌ๋ง๋ฃจ-Bold", | |
"์นดํ24 ์๋ค๋ชจ๋ค-Bold", "์ด๊ทธ๋ก์ฒด-Bold", "SFํจ๋ฐ๋", "ํ์ดํผ๋ก์ง-Bold" | |
], | |
value="ํ๋ฆฌํ ๋ค๋-Bold", | |
label="๋ฉ์ธ ์นดํผ ํฐํธ" | |
) | |
sub_font_choice = gr.Dropdown( | |
choices=[ | |
"ํ๋ฆฌํ ๋ค๋-Regular", "์์ค์ฝ์ด๋๋ฆผ-Regular", "๋ ธํ ์ฐ์ค-Regular", | |
"๋ฐฐ๋ฌ์๋ฏผ์กฑ ๋ํ์ฒด", "๋ฐฐ๋ฌ์๋ฏผ์กฑ ์ฃผ์์ฒด", "์ฌ๊ธฐ์ด๋ ์๋์ฒด", | |
"์จ๊ธ์ ์ฝ์ฝ์ฒด", "์จ๊ธ์ ๋ฐ๋คํ์ฒด", "์ด์ฌ๋ง๋ฃจ-Light", | |
"์นดํ24 ์๋ค๋ชจ๋ค-Regular", "์ด๊ทธ๋ก์ฒด-Light", "SFํจ๋ฐ๋", "ํ์ดํผ๋ก์ง-Regular" | |
], | |
value="ํ๋ฆฌํ ๋ค๋-Regular", | |
label="์๋ธ ์นดํผ ํฐํธ" | |
) | |
with gr.Group(): | |
gr.Markdown("### ์๋ ์์ ์ค์ ") | |
with gr.Row(): | |
manual_bg_color = gr.ColorPicker(label="๋ฐฐ๊ฒฝ์", value="#FFFFFF", interactive=True) | |
with gr.Row(): | |
manual_main_text_color = gr.ColorPicker(label="๋ฉ์ธ ํ ์คํธ์", value="#000000", interactive=True) | |
manual_sub_text_color = gr.ColorPicker(label="์๋ธ ํ ์คํธ์", value="#000000", interactive=True) | |
with gr.Group(): | |
gr.Markdown("### ์๋ ํฐํธ ํฌ๊ธฐ ์ค์ ") | |
with gr.Row(): | |
manual_main_font_size = gr.Slider( | |
minimum=20, maximum=200, value=100, step=5, | |
label="๋ฉ์ธ ํฐํธ ํฌ๊ธฐ (px)", interactive=True | |
) | |
manual_sub_font_size = gr.Slider( | |
minimum=15, maximum=120, value=55, step=5, | |
label="์๋ธ ํฐํธ ํฌ๊ธฐ (px)", interactive=True | |
) | |
with gr.Group(): | |
gr.Markdown("### ๐ ์ฌ๋ฐฑ ๋ฐ ๊ฐ๊ฒฉ ์กฐ์ ") | |
with gr.Row(): | |
top_bottom_margin = gr.Slider( | |
minimum=50, maximum=800, value=450, step=10, | |
label="์ํ ์ฌ๋ฐฑ (px)", | |
info="์ฌ๋ฐฑ-์นดํผ-์ฌ๋ฐฑ์ ์ฌ๋ฐฑ ํฌ๊ธฐ", | |
interactive=True | |
) | |
with gr.Row(): | |
text_gap = gr.Slider( | |
minimum=5, maximum=100, value=30, step=5, | |
label="๋ฉ์ธโ์๋ธ ๊ฐ๊ฒฉ (px)", | |
info="๋ฉ์ธ์นดํผ์ ์๋ธ์นดํผ ์ฌ์ด ๊ฐ๊ฒฉ", | |
interactive=True | |
) | |
# ํ์ฌ ์ ์ฉ๋ ์ฌ๋ฐฑ ์ ๋ณด ํ์ | |
margin_info = gr.Markdown( | |
"๐ก **ํ์ฌ ์ฌ๋ฐฑ ์ ๋ณด:** ์ด๋ฏธ์ง ์์ฑ ํ ์ค์ ์ ์ฉ๋ ์์น๊ฐ ํ์๋ฉ๋๋ค.", | |
visible=True | |
) | |
# ์์ฑ๋ ์ด๋ฏธ์ง ์น์ | |
with gr.Column(elem_classes="custom-frame"): | |
gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/1375/1375106.png"> ์์ฑ๋ ์ด๋ฏธ์ง</div>') | |
# ๐ฏ ํต์ฌ: Gradio ๊ธฐ๋ณธ ์ด๋ฏธ์ง ์ถ๋ ฅ (๋ค์ด๋ก๋ ๋ฒํผ ์๋ ํฌํจ) | |
output_image = gr.Image( | |
label="์์ฑ๋ ์ด๋ฏธ์ง", | |
show_download_button=True, # ๋ค์ด๋ก๋ ๋ฒํผ ํ์ | |
show_share_button=False, # ๊ณต์ ๋ฒํผ ์จ๊น | |
interactive=False, | |
type="pil", # PIL Image ํ์ ์ผ๋ก ์ค์ | |
elem_classes="image-container output-image-container" | |
) | |
# ๐ฏ ํต์ฌ: Gradio ๊ธฐ๋ณธ ํ์ผ ๋ค์ด๋ก๋ ์ปดํฌ๋ํธ | |
download_file = gr.File( | |
label="๐ฅ ์ด๋ฏธ์ง ๋ค์ด๋ก๋", | |
visible=True, | |
interactive=False | |
) | |
# ๐ฏ ํต์ฌ: ์๋ํฌ์ธํธ ๊ธฐ๋ฐ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ค | |
def update_copy_type_description(selected_type): | |
"""์๋ณธ๊ณผ ๋์ผํ ์นดํผ ํ์ ์ ํ์ ์ค๋ช ์ ๋ฐ์ดํธ""" | |
try: | |
client = get_client() | |
if not client: | |
return "โ ์๋ฒ ์ฐ๊ฒฐ ์คํจ" | |
result = client.predict( | |
selected_type=selected_type, | |
api_name="/update_copy_type_description" | |
) | |
return result | |
except Exception as e: | |
print(f"โ ์นดํผ ํ์ ์ค๋ช ์ ๋ฐ์ดํธ ์คํจ: {str(e)[:50]}...") | |
return f"โ ์ค๋ฅ: ์ค๋ช ์ ๋ฐ์ดํธ ์คํจ" | |
def handle_copy_generation(keyword, selected_type): | |
"""์๋ณธ๊ณผ ๋์ผํ ์นดํผ ์์ฑ ์ฒ๋ฆฌ""" | |
try: | |
client = get_client() | |
if not client: | |
return ("โ ์๋ฒ ์ฐ๊ฒฐ ์คํจ", {}, "", "", "", "", "", "", "", keyword.strip() if keyword else "") | |
result = client.predict( | |
keyword=keyword, | |
selected_type=selected_type, | |
api_name="/handle_copy_generation" | |
) | |
# ์๋ณธ๊ณผ ๋์ผํ ํํ ๋ฐํ (10๊ฐ ์์) | |
# [0] ์ํ๋ฉ์์ง, [1] copy_suggestions_state, [2-6] ์นดํผ1-5, [7-8] ๋ฉ์ธ/์๋ธ, [9] current_keyword_state | |
if len(result) >= 8: | |
# API์์ 8๊ฐ๋ฅผ ๋ฐ์ผ๋ฉด ์ถ๊ฐ ์ํ๊ฐ๋ค์ ๋ก์ปฌ์์ ์ฒ๋ฆฌ | |
return (result[0], {}, result[1], result[2], result[3], result[4], result[5], result[6], result[7], keyword.strip() if keyword else "") | |
else: | |
return result | |
except Exception as e: | |
print(f"โ ์นดํผ ์์ฑ ์คํจ: {str(e)[:50]}...") | |
error_msg = "โ ์นดํผ ์์ฑ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค" | |
return (error_msg, {}, "", "", "", "", "", "", "", keyword.strip() if keyword else "") | |
def handle_copy_selection(copy_suggestions_state, selected_type, selected_copy): | |
"""์๋ณธ๊ณผ ๋์ผํ ์นดํผ ์ ํ์ ๋ฉ์ธ/์๋ธ ํ ์คํธ๋ฐ์ค ์ ๋ฐ์ดํธ""" | |
try: | |
client = get_client() | |
if not client: | |
return ("", "") | |
result = client.predict( | |
selected_type=selected_type, | |
selected_copy=selected_copy, | |
api_name="/handle_copy_selection" | |
) | |
# ํํ ๋ฐํ (2๊ฐ ์์) | |
return result | |
except Exception as e: | |
print(f"โ ์นดํผ ์ ํ ์คํจ: {str(e)[:50]}...") | |
return ("", "") | |
def handle_image_generation(input_image, main_text, sub_text, color_mode, | |
main_font_choice, sub_font_choice, manual_bg_color, manual_main_text_color, | |
manual_sub_text_color, manual_main_font_size, manual_sub_font_size, | |
top_bottom_margin, text_gap, current_keyword): | |
"""์๋ณธ๊ณผ ๋์ผํ ์ด๋ฏธ์ง ์์ฑ ์ฒ๋ฆฌ (์ฌ๋ฐฑ ์กฐ์ ๊ธฐ๋ฅ ํฌํจ)""" | |
try: | |
client = get_client() | |
if not client: | |
return (None, color_mode, manual_bg_color, manual_main_text_color, | |
manual_sub_text_color, manual_main_font_size, manual_sub_font_size, | |
None, "โ ์๋ฒ ์ฐ๊ฒฐ ์คํจ") | |
# ์ด๋ฏธ์ง ํ์ผ ํธ๋ค๋ง | |
image_file = None | |
if input_image: | |
image_file = handle_file(input_image) | |
result = client.predict( | |
input_image=image_file, | |
main_text=main_text, | |
sub_text=sub_text, | |
color_mode=color_mode, | |
main_font_choice=main_font_choice, | |
sub_font_choice=sub_font_choice, | |
manual_bg_color=manual_bg_color, | |
manual_main_text_color=manual_main_text_color, | |
manual_sub_text_color=manual_sub_text_color, | |
manual_main_font_size=manual_main_font_size, | |
manual_sub_font_size=manual_sub_font_size, | |
top_bottom_margin=top_bottom_margin, | |
text_gap=text_gap, | |
api_name="/handle_image_generation" | |
) | |
# ์๋ณธ๊ณผ ๋์ผํ ํํ ๋ฐํ (9๊ฐ ์์) | |
# [0] ์ด๋ฏธ์ง, [1] ์์๋ชจ๋, [2] ๋ฐฐ๊ฒฝ์, [3] ๋ฉ์ธํ ์คํธ์, [4] ์๋ธํ ์คํธ์, | |
# [5] ๋ฉ์ธํฐํธํฌ๊ธฐ, [6] ์๋ธํฐํธํฌ๊ธฐ, [7] ๋ค์ด๋ก๋ํ์ผ, [8] ์ฌ๋ฐฑ์ ๋ณด | |
return result | |
except Exception as e: | |
print(f"โ ์ด๋ฏธ์ง ์์ฑ ์คํจ: {str(e)[:50]}...") | |
return (None, color_mode, manual_bg_color, manual_main_text_color, | |
manual_sub_text_color, manual_main_font_size, manual_sub_font_size, | |
None, "โ ์ด๋ฏธ์ง ์์ฑ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค") | |
# ์๋ณธ๊ณผ ๋์ผํ ์ด๋ฒคํธ ์ฐ๊ฒฐ | |
copy_type_selection.change( | |
fn=update_copy_type_description, | |
inputs=[copy_type_selection], | |
outputs=[copy_type_description] | |
) | |
generate_copy_btn.click( | |
fn=handle_copy_generation, | |
inputs=[product_keyword, copy_type_selection], | |
outputs=[copy_type_description, copy_suggestions_state, | |
copy1_display, copy2_display, copy3_display, copy4_display, copy5_display, | |
main_text, sub_text, current_keyword_state] | |
) | |
copy_selection.change( | |
fn=handle_copy_selection, | |
inputs=[copy_suggestions_state, copy_type_selection, copy_selection], | |
outputs=[main_text, sub_text] | |
) | |
generate_image_btn.click( | |
fn=handle_image_generation, | |
inputs=[input_image, main_text, sub_text, color_mode, | |
main_font_choice, sub_font_choice, manual_bg_color, manual_main_text_color, manual_sub_text_color, | |
manual_main_font_size, manual_sub_font_size, top_bottom_margin, text_gap, current_keyword_state], | |
outputs=[output_image, color_mode, manual_bg_color, manual_main_text_color, manual_sub_text_color, | |
manual_main_font_size, manual_sub_font_size, download_file, margin_info] | |
) | |
return app | |
if __name__ == "__main__": | |
app = main() | |
app.launch(share=False, inbrowser=True) |