Badge / app.py
openfree's picture
Update app.py
07f7a33 verified
raw
history blame
16.6 kB
# ==========================================================
# BadgeCraft ‑ Shields.io Badge Generator
# • ColorPicker 실시간 반영 (.input 이벤트)
# • CDN 캐시 무력화 ( _ts=<epoch> 파라미터 )
# ==========================================================
import urllib.parse
import time
import json
import gradio as gr
# ----------------------------------------------------------
# 1) Badge URL 생성 함수
# ----------------------------------------------------------
def generate_static_badge(label, message,
color, label_color,
logo, logo_color,
style, link):
"""Shields.io badge URL 과 HTML / 미리보기 카드 반환"""
# ── ① HEX 앞의 # 제거 ───────────────────────────────
strip = lambda c: c.lstrip("#") if isinstance(c, str) else c
color, label_color, logo_color = map(strip, (color, label_color, logo_color))
# ── ② 쿼리 파라미터 조립 + 캐시 버스터(_ts) ─────────
base = "https://img.shields.io/static/v1"
qs = {
"label": label,
"message": message,
"color": color,
"labelColor": label_color,
"logo": logo,
"logoColor": logo_color,
"style": style,
"_ts": str(int(time.time())) # ← 캐시 무력화
}
query = "&".join(f"{k}={urllib.parse.quote(v, safe='')}"
for k, v in qs.items() if v)
badge_url = f"{base}?{query}"
# ── ③ HTML 코드 & 미리보기 구성 ────────────────────
img_tag = f'<img src="{badge_url}" alt="badge">'
html_code = f'<a href="{link}" target="_blank">{img_tag}</a>' if link else img_tag
preview = (
"<div style='padding:30px;text-align:center;"
"border-radius:16px;background:linear-gradient(135deg,#f8f9fa,#e9ecef);"
"box-shadow:0 6px 12px rgba(0,0,0,.05);'>"
f"{html_code}</div>"
)
return html_code, preview
# ----------------------------------------------------------
# 2) Gradio UI
# ----------------------------------------------------------
with gr.Blocks(theme=gr.themes.Soft()) as demo:
# ---------- Global CSS ----------
gr.HTML("""
<style>
body{
background:linear-gradient(120deg,#f8f9fa,#e2eafc);
font-family:'Poppins','Noto Sans KR',sans-serif;
}
.gradio-container{
background:rgba(255,255,255,.85);
backdrop-filter:blur(10px);
border-radius:20px;
padding:24px;
box-shadow:0 10px 30px rgba(0,0,0,.08);
max-width:1000px;
margin:0 auto;
}
.gr-button{
background:linear-gradient(135deg,#a8dadc,#88c1e9)!important;
color:#1d3557!important;
border:none!important;
border-radius:10px!important;
font-weight:600!important;
transition:all .3s ease!important;
box-shadow:0 4px 10px rgba(138,198,209,.3)!important;
}
.gr-button:hover{
transform:translateY(-2px)!important;
box-shadow:0 6px 15px rgba(138,198,209,.4)!important;
}
.gr-textbox,.gr-select,.gr-color{
background:#e8f6f3!important;
border:2px solid #d9f0ea!important;
border-radius:12px!important;
transition:all .3s ease!important;
}
.gr-textbox:focus,.gr-select:focus,.gr-color:focus{
border-color:#a8dadc!important;
box-shadow:0 0 0 3px rgba(168,218,220,.25)!important;
}
label.block span{
color:#457b9d!important;
font-weight:600!important;
font-size:1rem!important;
}
h1{
color:#5e60ce;font-weight:800;letter-spacing:-.5px;
}
h3{color:#5e60ce;font-weight:600;}
.footer{
margin-top:30px;text-align:center;font-size:.9rem;color:#6d6875;
}
.badge-section{
background:rgba(255,255,255,.7);
border-radius:16px;
padding:20px;
box-shadow:0 4px 12px rgba(0,0,0,.03);
margin-bottom:24px;
border:1px solid rgba(230,240,255,.7);
}
.example-grid{
display:grid;
grid-template-columns:repeat(4,1fr);
gap:16px;margin-top:20px;
}
.example-item{
background:linear-gradient(135deg,#f1f8ff,#e8f4ff);
border-radius:12px;padding:16px;text-align:center;
cursor:pointer;transition:all .3s ease;border:2px solid transparent;
}
.example-item:hover{
transform:translateY(-3px);
box-shadow:0 8px 15px rgba(0,0,0,.05);
border-color:#a8dadc;
}
@media(max-width:768px){.example-grid{grid-template-columns:repeat(2,1fr);} }
@media(max-width:600px){.example-grid{grid-template-columns:1fr;} }
</style>
""")
# ---------- Header ----------
gr.HTML("""
<div style="text-align:center;margin-bottom:24px;">
<h1 style="font-size:2.8rem;margin-bottom:.2em;
background:linear-gradient(90deg,#5e60ce,#64dfdf);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;">
🎨 BadgeCraft
</h1>
<p style="font-size:1.2rem;margin:.5em 0;color:#457b9d;max-width:700px;margin:0 auto;">
Create beautiful badges with live preview and HTML snippet
</p>
<div style="margin-top:10px;display:flex;justify-content:center;gap:12px;flex-wrap:wrap;">
<span style="display:inline-block;background:#e9f5db;color:#588157;
padding:6px 12px;border-radius:30px;font-size:.9rem;">
<strong>✨ MIT License</strong>
</span>
<span style="display:inline-block;background:#d8f3dc;color:#2d6a4f;
padding:6px 12px;border-radius:30px;font-size:.9rem;">
<strong>👥 Created by OpenFreeAI Team</strong>
</span>
</div>
</div>
""")
# ---------- Tabs ----------
with gr.Tabs():
# ▷▷ Badge Generator
with gr.TabItem("Badge Generator"):
# ===== 좌측 입력 =====
with gr.Row():
with gr.Column():
with gr.Group(elem_classes="badge-section"):
gr.HTML("<h3 style='margin-top:0;margin-bottom:16px;font-size:1.3rem;'>✏️ Badge Settings</h3>")
label = gr.Textbox(label="Label", value="Discord", lines=1, elem_id="label-input")
message = gr.Textbox(label="Message", value="Join our community", lines=1, elem_id="message-input")
logo = gr.Textbox(label="Logo", value="discord", lines=1, elem_id="logo-input")
style = gr.Dropdown(label="Style",
choices=["flat","flat-square","plastic","for-the-badge","social"],
value="for-the-badge", elem_id="style-input")
color = gr.ColorPicker(label="Background Color", value="#5865F2", elem_id="color-input")
label_color = gr.ColorPicker(label="Label Background Color",value="#99AAFF", elem_id="label-color-input")
logo_color = gr.ColorPicker(label="Logo Color", value="#ffffff", elem_id="logo-color-input")
link = gr.Textbox(label="Link (URL)", value="https://discord.gg/openfreeai", lines=1, elem_id="link-input")
# ===== 우측 미리보기 / HTML =====
with gr.Column():
with gr.Group(elem_classes="badge-section"):
gr.HTML("<h3 style='margin-top:0;margin-bottom:16px;font-size:1.3rem;'>👁️ Preview</h3>")
out_preview = gr.HTML()
with gr.Group(elem_classes="badge-section"):
gr.HTML("<h3 style='margin-top:0;margin-bottom:16px;font-size:1.3rem;'>💻 HTML Code</h3>")
out_code = gr.Code(language="html", lines=3)
# ===== 예제 데이터 & 그리드 =====
examples = [
["Discord","Openfree AI","#5865F2","#99AAFF","discord","white","for-the-badge","https://discord.gg/openfreeai"],
["X.com","Follow us","#1DA1F2","#00CFFF","x","white","for-the-badge","https://x.com/openfree_ai"],
["Collections","Explore","#FFB300","#FFF176","huggingface","black","for-the-badge",
"https://huggingface.co/collections/VIDraft/best-open-ai-services-68057e6e312880ea92abaf4c"],
["GitHub","Star us","#0A0A0A","#39FF14","github","white","for-the-badge","https://github.com/openfreeai"],
["YouTube","Watch now","#E50000","#FF5E5E","youtube","white","for-the-badge","https://www.youtube.com/@AITechTree"],
["Facebook","Like us","#1877F2","#6FAFFF","facebook","white","for-the-badge","https://www.facebook.com/profile.php?id=61575353674679"],
["Instagram","友情 萬世","#E4405F","#FF77A9","instagram","white","for-the-badge","https://www.instagram.com/openfree_ai/"],
["Threads","함께 즐겨요.","#000000","#FF00FF","threads","white","for-the-badge","https://www.threads.net/@openfree_ai"],
]
# 예제 그리드 HTML
html_items = '<div class="example-grid">'
for idx, ex in enumerate(examples):
badge_url = "https://img.shields.io/static/v1?" + "&".join([
f"label={urllib.parse.quote(ex[0], safe='')}",
f"message={urllib.parse.quote(ex[1], safe='')}",
f"color={urllib.parse.quote(ex[2].lstrip('#'), safe='')}",
f"labelColor={urllib.parse.quote(ex[3].lstrip('#'), safe='')}",
f"logo={urllib.parse.quote(ex[4], safe='')}",
f"logoColor={urllib.parse.quote(ex[5], safe='')}",
f"style={urllib.parse.quote(ex[6], safe='')}",
])
html_items += f'''
<div class="example-item" onclick="applyExample({idx})">
<img src="{badge_url}" alt="{ex[0]} badge" style="margin-bottom:8px;">
<div style="font-size:.9rem;color:#457b9d;">{ex[0]}</div>
</div>
'''
html_items += '</div>'
# -------- React‑controlled input hack & 예제 적용 JS --------
hack_js = """
function updateReactValue(el,val){
if(!el)return;
let proto=window.HTMLInputElement.prototype;
if(el.tagName==="TEXTAREA")proto=window.HTMLTextAreaElement.prototype;
const desc=Object.getOwnPropertyDescriptor(proto,"value");
desc.set.call(el,val);
el.dispatchEvent(new Event("input",{bubbles:true}));
el.dispatchEvent(new Event("change",{bubbles:true}));
}
function toHexIfNeeded(c){
if(!c)return"#000000";
if(c.toLowerCase()==="white")return"#ffffff";
if(c.toLowerCase()==="black")return"#000000";
return c;
}
function setColor(elemId,val){
const wrap=document.getElementById(elemId);
if(!wrap)return;
let input=wrap.querySelector("input[type='color']");
if(!input)input=wrap.querySelector("input[type='text']");
if(input)updateReactValue(input,val);
}
"""
apply_example_js = (
"<script>\n" + hack_js +
"const examples=" + json.dumps(examples) + ";\n"
"function applyExample(i){\n"
" const ex=examples[i];\n"
" updateReactValue(document.querySelector('#label-input textarea,#label-input input'),ex[0]);\n"
" updateReactValue(document.querySelector('#message-input textarea,#message-input input'),ex[1]);\n"
" setColor('color-input',toHexIfNeeded(ex[2]));\n"
" setColor('label-color-input',toHexIfNeeded(ex[3]));\n"
" updateReactValue(document.querySelector('#logo-input textarea,#logo-input input'),ex[4]);\n"
" setColor('logo-color-input',toHexIfNeeded(ex[5]));\n"
" const sel=document.querySelector('#style-input select');\n"
" if(sel){sel.value=ex[6];sel.dispatchEvent(new Event('change',{bubbles:true}));}\n"
" updateReactValue(document.querySelector('#link-input textarea,#link-input input'),ex[7]);\n"
"}\n</script>"
)
gr.HTML(html_items + apply_example_js)
# ===== 초기 로드 & 실시간 연결 =====
demo.load(
fn=generate_static_badge,
inputs=[label, message, color, label_color, logo, logo_color, style, link],
outputs=[out_code, out_preview]
)
# ── 텍스트/드롭다운: change 이벤트
for comp in [label, message, logo, style, link]:
comp.change(
fn=generate_static_badge,
inputs=[label, message, color, label_color, logo, logo_color, style, link],
outputs=[out_code, out_preview]
)
# ── ColorPicker: input 이벤트
for comp in [color, label_color, logo_color]:
comp.input(
fn=generate_static_badge,
inputs=[label, message, color, label_color, logo, logo_color, style, link],
outputs=[out_code, out_preview]
)
# ▷▷ Help 탭
with gr.TabItem("Help"):
gr.HTML('''
<div style="padding:20px;background:rgba(255,255,255,.7);
border-radius:16px;box-shadow:0 4px 12px rgba(0,0,0,.03);">
<h3 style="color:#5e60ce;margin-top:0;">📋 How to Use BadgeCraft</h3>
<h4 style="color:#457b9d;margin-bottom:8px;">✨ What are Badges?</h4>
<p>Badges are small visual indicators that can be used in README files, websites, and documentation. Shields.io badges display project status, social links, version info, and more.</p>
<h4 style="color:#457b9d;margin-bottom:8px;">🛠️ Basic Settings</h4>
<ul>
<li><strong>Label</strong>: Text on the left side (e.g., "Discord")</li>
<li><strong>Message</strong>: Text on the right side</li>
<li><strong>Logo</strong>: A Simple‑Icons name (<a href="https://simpleicons.org" target="_blank">List</a>)</li>
<li><strong>Style</strong>: Badge shape (flat, plastic, for‑the‑badge …)</li>
</ul>
<h4 style="color:#457b9d;margin-bottom:8px;">🎨 Color Settings</h4>
<ul>
<li><strong>Background Color</strong>: right‑side color</li>
<li><strong>Label Background Color</strong>: left‑side color</li>
<li><strong>Logo Color</strong>: logo tint (white / black / #HEX)</li>
</ul>
<h4 style="color:#457b9d;margin-bottom:8px;">🔗 Using the HTML</h4>
<p>Copy the generated HTML and paste it into your site, blog, or GitHub README.<br>
GitHub also accepts raw Markdown: <code>![alt](badgeURL)</code></p>
<h4 style="color:#457b9d;margin-bottom:8px;">💡 Tips</h4>
<ul>
<li>Click any example tile to auto‑fill all fields</li>
<li>Preview refreshes live as you type or pick colors</li>
<li>Over 2 000 Simple‑Icons logos are supported</li>
</ul>
</div>
''')
# ---------- Footer ----------
gr.HTML('''
<div class="footer">
<p>© 2023‑2025 BadgeCraft | MIT License | 
<a href="https://discord.gg/openfreeai" target="_blank" style="color:#5e60ce;">Discord</a>
</p>
</div>
''')
# ----------------------------------------------------------
# 3) Launch
# ----------------------------------------------------------
if __name__ == "__main__":
demo.launch()