UI 엔진연결(gradio_client) 반영
Browse files- app.py +200 -191
- requirements.txt +3 -2
app.py
CHANGED
|
@@ -1,191 +1,200 @@
|
|
| 1 |
-
# -*- coding: utf-8 -*-
|
| 2 |
-
"""치토스(CHITOS) 인스턴트 스캔 — 공개 UI(얇은 클라이언트).
|
| 3 |
-
점검 로직·채점은 비공개 백엔드(치토스 서버)에서 수행 → 이 공개 소스엔 방법이 없습니다.
|
| 4 |
-
2단계 인증: 회사/조직 도메인 메일로 가입한 Hugging Face 계정으로 로그인 → 이메일 도메인이
|
| 5 |
-
진단 대상과 일치하면 심화 진단 승인(별도 코드 발송 없음).
|
| 6 |
-
서비스: 퀀트ID(QuantID) · 엔진: 치토스(CHITOS)."""
|
| 7 |
-
import os, html
|
| 8 |
-
from urllib.parse import urlparse
|
| 9 |
-
import requests
|
| 10 |
-
import gradio as gr
|
| 11 |
-
from huggingface_hub import whoami
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
def
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
for
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
if
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
<
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
<
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
<
|
| 110 |
-
<
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
<
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
if
|
| 135 |
-
return _note(
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
gr.
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""치토스(CHITOS) 인스턴트 스캔 — 공개 UI(얇은 클라이언트).
|
| 3 |
+
점검 로직·채점은 비공개 백엔드(치토스 서버)에서 수행 → 이 공개 소스엔 방법이 없습니다.
|
| 4 |
+
2단계 인증: 회사/조직 도메인 메일로 가입한 Hugging Face 계정으로 로그인 → 이메일 도메인이
|
| 5 |
+
진단 대상과 일치하면 심화 진단 승인(별도 코드 발송 없음).
|
| 6 |
+
서비스: 퀀트ID(QuantID) · 엔진: 치토스(CHITOS)."""
|
| 7 |
+
import os, html, json
|
| 8 |
+
from urllib.parse import urlparse
|
| 9 |
+
import requests
|
| 10 |
+
import gradio as gr
|
| 11 |
+
from huggingface_hub import whoami
|
| 12 |
+
from gradio_client import Client
|
| 13 |
+
|
| 14 |
+
ENGINE = os.environ.get("CHITOS_ENGINE_REPO", "")
|
| 15 |
+
HFTOKEN = os.environ.get("HF_TOKEN", "")
|
| 16 |
+
CONSULT = os.environ.get("CHITOS_CONSULT_ENDPOINT", "")
|
| 17 |
+
_eng_client = None
|
| 18 |
+
|
| 19 |
+
def _engine():
|
| 20 |
+
global _eng_client
|
| 21 |
+
if _eng_client is None and ENGINE:
|
| 22 |
+
_eng_client = Client(ENGINE, hf_token=HFTOKEN or None)
|
| 23 |
+
return _eng_client
|
| 24 |
+
|
| 25 |
+
GCOL = {"A+": "#067647", "A": "#067647", "B": "#2563eb", "C": "#b54708", "D": "#b54708", "F": "#b42318"}
|
| 26 |
+
SEV = {"hi": ("#b42318", "#fef3f2", "높음"), "mid": ("#b54708", "#fffaeb", "보완"), "low": ("#8a6d00", "#fdfce8", "낮음")}
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _api(path, payload):
|
| 30 |
+
c = _engine()
|
| 31 |
+
if c is None:
|
| 32 |
+
return {"error": "진단 엔진이 아직 연결되지 않았습니다(운영 설정 대기)."}
|
| 33 |
+
try:
|
| 34 |
+
res = c.predict(payload.get("url", ""), api_name="/" + path.strip("/"))
|
| 35 |
+
return json.loads(res)
|
| 36 |
+
except Exception as e:
|
| 37 |
+
return {"error": "진단 엔진 호출 실패(첫 호출은 기동에 시간이 걸릴 수 있습니다): %s" % str(e)[:90]}
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _domain(u):
|
| 41 |
+
u = (u or "").strip()
|
| 42 |
+
if "://" not in u:
|
| 43 |
+
u = "https://" + u
|
| 44 |
+
return (urlparse(u).hostname or "").replace("www.", "")
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def _grade_badge(g, label, sub):
|
| 48 |
+
letter, score = g[0], g[1]
|
| 49 |
+
col = GCOL.get(letter, "#b54708")
|
| 50 |
+
return ('<div style="flex:1;text-align:center;border:1px solid #e4e9ef;border-radius:14px;padding:18px">'
|
| 51 |
+
'<div style="font-size:13px;color:#6b7280;font-weight:700">%s</div>'
|
| 52 |
+
'<div style="font-size:52px;font-weight:900;line-height:1;color:%s;margin:6px 0">%s</div>'
|
| 53 |
+
'<div style="font-size:12px;color:#9ca3af">%s / 100 · %s</div></div>') % (label, col, letter, score, sub)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _cards(items, empty):
|
| 57 |
+
if not items:
|
| 58 |
+
return '<div style="color:#067647;font-weight:700;padding:8px 0">%s</div>' % empty
|
| 59 |
+
out = ""
|
| 60 |
+
for sev, title, detail in items:
|
| 61 |
+
c, bg, lab = SEV.get(sev, SEV["low"])
|
| 62 |
+
out += ('<div style="border:1px solid #e4e9ef;border-left:4px solid %s;border-radius:9px;padding:10px 13px;margin:7px 0;background:%s">'
|
| 63 |
+
'<b style="color:%s">[%s] %s</b><div style="color:#4b5563;font-size:13px;margin-top:3px">%s</div></div>') % (c, bg, c, lab, html.escape(str(title)), html.escape(str(detail)))
|
| 64 |
+
return out
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def _traffic(matrix):
|
| 68 |
+
rows = ""
|
| 69 |
+
for name, code in matrix.items():
|
| 70 |
+
ok = code == "200"
|
| 71 |
+
dot = "#16a34a" if ok else ("#dc2626" if code in ("403", "401", "429", "차단") else "#9ca3af")
|
| 72 |
+
txt = "정상" if ok else ("차단" if code in ("403", "401", "429", "차단") else code)
|
| 73 |
+
rows += ('<div style="display:flex;align-items:center;gap:9px;padding:6px 10px;border:1px solid #eef1f6;border-radius:8px">'
|
| 74 |
+
'<span style="width:11px;height:11px;border-radius:50%%;background:%s;flex:none"></span>'
|
| 75 |
+
'<span style="flex:1;font-size:12.5px">%s</span><span style="font-size:11.5px;color:#6b7280">%s</span></div>') % (dot, html.escape(str(name)), txt)
|
| 76 |
+
return '<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:7px">%s</div>' % rows
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def _note(msg, sev="ok"):
|
| 80 |
+
c = {"ok": "#067647", "mid": "#b54708", "hi": "#b42318"}.get(sev, "#067647")
|
| 81 |
+
bg = {"ok": "#ecfdf3", "mid": "#fffaeb", "hi": "#fef3f2"}.get(sev, "#ecfdf3")
|
| 82 |
+
return '<div style="border:1px solid #e4e9ef;border-left:4px solid %s;background:%s;border-radius:9px;padding:11px 14px;font-family:sans-serif;color:#374151">%s</div>' % (c, bg, msg)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _render(d, deep=False):
|
| 86 |
+
if d.get("error"):
|
| 87 |
+
return _note("⚠ " + html.escape(d["error"]), "hi")
|
| 88 |
+
if d.get("blocked"):
|
| 89 |
+
return ('<div style="font-family:sans-serif;max-width:700px;margin:0 auto;border:1px solid #e4e9ef;border-radius:12px;padding:20px">'
|
| 90 |
+
'<h3 style="color:#067647">🛡 이 사이트는 자동 공격 차단(WAF)이 작동 중입니다</h3>'
|
| 91 |
+
'<p>비정상 접근을 탐지해 차단했습니다. <b>방어가 잘 되어 있다는 좋은 신호</b>입니다. 치토스는 차단을 우회하지 않습니다(읽기전용). 정밀 점검은 소유자 허가 후 진행합니다.</p></div>')
|
| 92 |
+
meta = d.get("meta", {})
|
| 93 |
+
good = "".join('<span style="display:inline-block;background:#ecfdf3;color:#067647;border-radius:999px;padding:3px 11px;font-size:12px;margin:3px 4px 0 0">✓ %s</span>' % html.escape(str(g)) for g in d.get("good", [])) or "—"
|
| 94 |
+
deep_html = ""
|
| 95 |
+
if deep and d.get("deep"):
|
| 96 |
+
dp = d["deep"]
|
| 97 |
+
deep_html = f"""<h3 style="color:#2e1065;border-bottom:2px solid #6d28d9;padding-bottom:6px;margin-top:20px">🔎 심화: 다중 페이지 ({dp['pages']}개)</h3>
|
| 98 |
+
<ul style="font-size:13px;color:#374151;line-height:1.7">
|
| 99 |
+
<li>제목 <b>{dp['uniq_titles']}종 고유 / {dp['dup_titles']}개 중복</b> {'⚠ 중복 많음' if dp['dup_titles']>0 else '✓'}</li>
|
| 100 |
+
<li>대제목(H1) 합계 <b>{dp['h1_total']}개</b></li>
|
| 101 |
+
<li>구조화 데이터 합계 <b>{dp['jsonld_total']}개</b> {'⚠ 서비스·FAQ 추가 권장' if dp['jsonld_total']<2 else '✓'}</li>
|
| 102 |
+
</ul>"""
|
| 103 |
+
return f"""
|
| 104 |
+
<div style="font-family:'Malgun Gothic',sans-serif;max-width:860px;margin:0 auto">
|
| 105 |
+
<div style="display:flex;gap:14px;margin-bottom:6px">
|
| 106 |
+
{_grade_badge(d['sec_grade'],"🛡 보안 등급","자물쇠 "+str(d.get('locks_on',0))+"/6 켜짐")}
|
| 107 |
+
{_grade_badge(d['geo_grade'],"🔍 AI 가시성(GEO)","검색·AI 노출")}
|
| 108 |
+
</div>
|
| 109 |
+
<div style="font-size:12.5px;color:#6b7280;text-align:center;margin-bottom:16px">대상 <b>{html.escape(str(d.get('host','')))}</b> · 비침투(읽기전용) · 치토스(CHITOS)</div>
|
| 110 |
+
<h3 style="color:#2e1065;border-bottom:2px solid #6d28d9;padding-bottom:6px">🛡 보안 점검</h3>
|
| 111 |
+
{_cards(d.get('sec',[]), "✓ 외부에서 보이는 보안 위생 문제 없음")}
|
| 112 |
+
<h3 style="color:#2e1065;border-bottom:2px solid #6d28d9;padding-bottom:6px;margin-top:20px">🤖 AI 로봇이 들어올 수 있나</h3>
|
| 113 |
+
{_traffic(d.get('matrix',{}))}
|
| 114 |
+
<h3 style="color:#2e1065;border-bottom:2px solid #6d28d9;padding-bottom:6px;margin-top:20px">🔍 검색·AI 가시성(GEO)</h3>
|
| 115 |
+
{_cards(d.get('geo',[]), "✓ 기계 판독 신호 양호")}
|
| 116 |
+
<div style="font-size:12px;color:#6b7280;margin-top:6px">제목: {html.escape(str(meta.get('title') or '(없음)'))} · H1 {meta.get('h1',0)}개 · 구조화 {meta.get('jsonld',0)}개 · 홈 {meta.get('size_kb',0)}KB</div>
|
| 117 |
+
{deep_html}
|
| 118 |
+
<h3 style="color:#067647;margin-top:20px">✓ 잘 되어 있는 점</h3><div>{good}</div>
|
| 119 |
+
<div style="background:#f4f0ff;border:1px dashed #b89cff;border-radius:12px;padding:14px 16px;margin-top:18px">
|
| 120 |
+
<b style="color:#6d28d9">더 깊은 점검이 필요하다면?</b><br>
|
| 121 |
+
<span style="font-size:13px;color:#374151">이 진단은 <b>비침투(읽기전용)</b>입니다. SQL삽입·파일업로드·인증우회 등 실제 취약점과 금융보안원 17종 에이전트 보안 정밀 점검, 전체 PDF 리포트는 <b>3단계 상담</b>으로 제공합니다.</span>
|
| 122 |
+
</div>
|
| 123 |
+
<div style="text-align:center;color:#9ca3af;font-size:11px;margin-top:12px">서비스: 퀀트ID(QuantID) · 엔진: 치토스(CHITOS) · 비침투·비파괴</div>
|
| 124 |
+
</div>"""
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def free_scan(url):
|
| 128 |
+
if not (url or "").strip():
|
| 129 |
+
return _note("진단할 주소를 입력하세요. 예: example.com", "mid")
|
| 130 |
+
return _render(_api("/scan", {"url": url}))
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def deep_scan(url, profile: gr.OAuthProfile | None = None, oauth_token: gr.OAuthToken | None = None):
|
| 134 |
+
if profile is None or oauth_token is None:
|
| 135 |
+
return _note('먼저 위의 <b>"Hugging Face로 로그인"</b> 버튼으로 로그인하세요. (회사/조직 도메인 메일로 가입한 계정)', "mid")
|
| 136 |
+
if not (url or "").strip():
|
| 137 |
+
return _note("진단할 주소를 입력하세요.", "mid")
|
| 138 |
+
try:
|
| 139 |
+
me = whoami(token=oauth_token.token)
|
| 140 |
+
email = (me.get("email") or "").lower()
|
| 141 |
+
except Exception:
|
| 142 |
+
email = ""
|
| 143 |
+
if not email:
|
| 144 |
+
return _note("로그인 계정의 <b>인증된 이메일</b>을 읽지 못했습니다. HF 계정 이메일을 확인·인증 후 다시 시도하세요.", "hi")
|
| 145 |
+
edom = email.split("@")[-1]
|
| 146 |
+
tdom = _domain(url)
|
| 147 |
+
if edom != tdom and not tdom.endswith("." + edom) and not edom.endswith("." + tdom):
|
| 148 |
+
return _note("도메인 소유 확인 실패. 진단 대상(<b>%s</b>)의 도메인 이메일로 가입한 HF 계정으로 로그인해야 합니다. 현재 계정 이메일 도메인: <b>%s</b>" % (html.escape(tdom), html.escape(edom)), "hi")
|
| 149 |
+
d = _api("/deep", {"url": url})
|
| 150 |
+
pre = _note("✓ 도메인 소유 인증 완료(<b>%s</b>) — 심화 진단을 수행했습니다. (비침투)" % html.escape(email), "ok")
|
| 151 |
+
return pre + _render(d, deep=True)
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def consult(name, company, email, phone, msg):
|
| 155 |
+
if not (name and email):
|
| 156 |
+
return _note("이름과 이메일은 필수입니다.", "mid")
|
| 157 |
+
if not CONSULT:
|
| 158 |
+
return _note("상담 접수처가 아직 설정되지 않았습니다. 잠시 후 다시 시도해 주세요.", "mid")
|
| 159 |
+
try:
|
| 160 |
+
rr = requests.post(CONSULT, json={"이름": name, "회사": company or "-", "이메일": email,
|
| 161 |
+
"연락처": phone or "-", "문의": msg or "-", "_subject": "[CHITOS] 정밀진단 상담신청"}, timeout=15)
|
| 162 |
+
return _note("상담 신청이 접수됐습니다. 퀀트ID(QuantID)에서 곧 연락드립니다. 감사합니다.", "ok") if rr.status_code < 300 else _note("접수 중 문제가 발생했습니다. 다시 시도해 주세요.", "mid")
|
| 163 |
+
except Exception:
|
| 164 |
+
return _note("접수 연결 실패. 잠시 후 다시 시도해 주세요.", "mid")
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
CSS = "#title{text-align:center}.gradio-container{max-width:980px!important;margin:0 auto}"
|
| 168 |
+
with gr.Blocks(title="CHITOS 인스턴트 스캔", css=CSS, theme=gr.themes.Soft(primary_hue="purple")) as demo:
|
| 169 |
+
gr.HTML('<div id="title" style="padding:10px 0 4px"><div style="font-size:13px;letter-spacing:5px;color:#8b5cf6;font-weight:800">C H I T O S</div>'
|
| 170 |
+
'<h1 style="margin:6px 0;color:#2e1065">보안 · AI가시성 진단</h1>'
|
| 171 |
+
'<div style="color:#6b7280">① 무료 즉시 진단 → ② 도메인 메일 HF 로그인 심화 진단 → ③ 정밀 진단 상담</div></div>')
|
| 172 |
+
with gr.Tabs():
|
| 173 |
+
with gr.Tab("① 무료 즉시 진단 (FREE)"):
|
| 174 |
+
gr.HTML('<div style="color:#4b5563;font-size:13px;margin:4px 0 8px">주소만 넣으세요. 공격 없이(읽기전용) 보안 등급과 "AI가 당신을 읽을 수 있는지"를 바로.</div>')
|
| 175 |
+
with gr.Row():
|
| 176 |
+
u1 = gr.Textbox(label="", placeholder="example.com", scale=4, container=False)
|
| 177 |
+
b1 = gr.Button("무료 진단", variant="primary", scale=1)
|
| 178 |
+
o1 = gr.HTML()
|
| 179 |
+
b1.click(free_scan, u1, o1); u1.submit(free_scan, u1, o1)
|
| 180 |
+
with gr.Tab("② 도메인 인증 심화 진단"):
|
| 181 |
+
gr.HTML('<div style="color:#4b5563;font-size:13px;margin:4px 0 8px"><b>회사/조직 도메인 이메일로 가입한 Hugging Face 계정</b>으로 로그인하세요. '
|
| 182 |
+
'그 이메일 도메인이 진단 대상과 일치하면(소유 확인) 다중 페이지 심화 진단이 열립니다. 별도 인증코드 발송은 없습니다.</div>')
|
| 183 |
+
gr.LoginButton()
|
| 184 |
+
u2 = gr.Textbox(label="진단할 주소", placeholder="example.com")
|
| 185 |
+
b2 = gr.Button("인증 후 심화 진단", variant="primary")
|
| 186 |
+
o2 = gr.HTML()
|
| 187 |
+
b2.click(deep_scan, inputs=u2, outputs=o2)
|
| 188 |
+
with gr.Tab("③ 정밀 진단 상담"):
|
| 189 |
+
gr.HTML('<div style="color:#4b5563;font-size:13px;margin:4px 0 8px">금융보안원 17종 에이전트 보안·소스 코드 취약점·능동 점검 등 <b>정밀 진단</b> 상담 신청.</div>')
|
| 190 |
+
with gr.Row():
|
| 191 |
+
n = gr.Textbox(label="이름*"); co = gr.Textbox(label="회사")
|
| 192 |
+
with gr.Row():
|
| 193 |
+
em = gr.Textbox(label="이메일*"); ph = gr.Textbox(label="연락처")
|
| 194 |
+
mg = gr.Textbox(label="문의 내용", lines=3)
|
| 195 |
+
bc = gr.Button("상담 신청", variant="primary"); oc = gr.HTML()
|
| 196 |
+
bc.click(consult, [n, co, em, ph, mg], oc)
|
| 197 |
+
gr.HTML('<div style="text-align:center;color:#9ca3af;font-size:11px;margin-top:12px">비침투·비파괴 · 타인 사이트 무단 정밀점검 금지 · 능동 점검은 도메인 인증·허가 후 · 서비스 퀀트ID(QuantID) · 엔진 치토스(CHITOS)</div>')
|
| 198 |
+
|
| 199 |
+
if __name__ == "__main__":
|
| 200 |
+
demo.launch()
|
requirements.txt
CHANGED
|
@@ -1,2 +1,3 @@
|
|
| 1 |
-
gradio==5.9.1
|
| 2 |
-
requests==2.32.3
|
|
|
|
|
|
| 1 |
+
gradio==5.9.1
|
| 2 |
+
requests==2.32.3
|
| 3 |
+
gradio_client==1.5.2
|