feat: free tab now renders full professional report (locks gauge, meta cards, 3-axis sections, priorities) + upsell; deep tab keeps full-site differentiation
Browse files
app.py
CHANGED
|
@@ -110,6 +110,39 @@ def _scorebar(label, g):
|
|
| 110 |
label, s, col, col, col, L, s)
|
| 111 |
|
| 112 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
def _cards(items, empty, lang):
|
| 114 |
if not items:
|
| 115 |
return '<div style="color:#067647;font-weight:700;padding:8px 0">%s</div>' % empty
|
|
@@ -220,7 +253,7 @@ def build_teaser(d, lang="ko"):
|
|
| 220 |
'<div style="text-align:center;color:#9ca3af;font-size:11px;margin-top:12px">' + _L("서비스: 퀀트ID(QuantID) · 엔진: 치토스(CHITOS)", "Service: QuantID · Engine: CHITOS", lang) + '</div></div>')
|
| 221 |
|
| 222 |
|
| 223 |
-
def build_report(d, deep=False, verified_email=None, lang="ko"):
|
| 224 |
if d.get("error"):
|
| 225 |
return _note("⚠ " + html.escape(_P(d["error"], lang)), "hi")
|
| 226 |
if d.get("blocked"):
|
|
@@ -305,13 +338,14 @@ def build_report(d, deep=False, verified_email=None, lang="ko"):
|
|
| 305 |
+ '<div style="flex:1;border:1px solid #e4e9ef;border-radius:10px;padding:10px"><b style="color:#b54708;font-size:22px">%d</b><div style="font-size:11px;color:#6b7280">%s</div></div>' % (nmid, _L("보완", "Advisory", lang))
|
| 306 |
+ '<div style="flex:1;border:1px solid #e4e9ef;border-radius:10px;padding:10px"><b style="color:#8a6d00;font-size:22px">%d</b><div style="font-size:11px;color:#6b7280">%s</div></div>' % (nlow, _L("낮음", "Low", lang))
|
| 307 |
+ '</div>'
|
| 308 |
-
+ _h2("🛡 " + _L("보안 점검", "Security checks", lang)) + _cards(sec, _L("✓ 외부에서 보이는 보안 위생 문제 없음", "✓ No externally visible security-hygiene issues", lang), lang)
|
| 309 |
-
+ _h2("📈 " + _L("SEO 토대 (검색 노출)", "SEO foundation (search visibility)", lang)) + _cards(seo, _L("✓ SEO 기본 요소 양호", "✓ SEO fundamentals are healthy", lang), lang)
|
| 310 |
+ _h2("🤖 " + _L("AI 로봇 접근 (AI 가시성)", "AI crawler access (AI visibility)", lang))
|
| 311 |
+ '<div style="font-size:12.5px;color:#4b5563;margin:4px 0 8px">' + _L("생성형 AI가 당신을 인용하려면 로봇이 먼저 들어와야 합니다.", "For generative AI to cite you, its crawlers must get in first.", lang) + '</div>'
|
| 312 |
+ _traffic(d.get('matrix', {}), lang) + _cards(geo, _L("✓ 기계 판독 신호 양호", "✓ Machine-readability signals are healthy", lang), lang)
|
| 313 |
+ '<div style="font-size:12px;color:#6b7280;margin-top:6px">' + _L("제목", "Title", lang) + ': ' + title80 + ' · H1 ' + str(meta.get('h1', 0)) + ' · ' + _L("구조화", "Schema", lang) + ' ' + str(meta.get('jsonld', 0)) + ' · ' + _L("홈", "Home", lang) + ' ' + str(meta.get('size_kb', 0)) + 'KB · alt ' + str(meta.get('alt_cov', 0)) + '%</div>'
|
| 314 |
+ deep_html
|
|
|
|
| 315 |
+ _h2("✓ " + _L("잘 되어 있는 점", "What's done well", lang)) + '<div>' + good + '</div>'
|
| 316 |
+ _h2("📌 " + _L("조치 우선순위", "Action priorities", lang)) + _priority(allf, lang)
|
| 317 |
+ '<div style="background:#f4f0ff;border:1px dashed #b89cff;border-radius:12px;padding:14px 16px;margin-top:16px"><b style="color:#6d28d9">' + _L("정밀 진단 안내", "About in-depth diagnosis", lang) + '</b><br><span style="font-size:13px;color:#374151">' + _L(
|
|
@@ -352,7 +386,7 @@ def free_scan(url, lang):
|
|
| 352 |
if not (url or "").strip():
|
| 353 |
return _note(_L("진단할 주소를 입력하세요. 예: example.com", "Enter a URL to scan. e.g. example.com", lang), "mid"), None
|
| 354 |
d = _api("/scan", {"url": url})
|
| 355 |
-
return
|
| 356 |
|
| 357 |
|
| 358 |
def deep_scan(url, lang, profile: gr.OAuthProfile | None = None, oauth_token: gr.OAuthToken | None = None):
|
|
@@ -593,7 +627,7 @@ def footer_html(lang):
|
|
| 593 |
|
| 594 |
def relang(label, last_scan, last_deep):
|
| 595 |
lang = "en" if str(label).startswith("Eng") else "ko"
|
| 596 |
-
o1u = gr.update(value=
|
| 597 |
o2u = gr.update(value=build_report(last_deep["d"], deep=True, verified_email=last_deep.get("email"), lang=lang)) if last_deep else gr.update()
|
| 598 |
return (lang, gr.update(value=label), header_html(lang), desc1(lang), desc2(lang), desc3(lang),
|
| 599 |
area_help(lang), guideline_html(lang), footer_html(lang), o1u, o2u)
|
|
|
|
| 110 |
label, s, col, col, col, L, s)
|
| 111 |
|
| 112 |
|
| 113 |
+
def _locks(n, lang):
|
| 114 |
+
cells = ""
|
| 115 |
+
for i in range(6):
|
| 116 |
+
on = i < n
|
| 117 |
+
cells += ('<div style="flex:1;text-align:center;border:1px solid #e4e9ef;border-radius:8px;padding:7px 2px;background:%s">'
|
| 118 |
+
'<div style="font-size:18px">%s</div></div>') % ("#ecfdf3" if on else "#fef3f2", "🔒" if on else "🔓")
|
| 119 |
+
cap = _L("보안 응답 헤더 6종 중 <b>%d개</b> 적용", "<b>%d of 6</b> security response headers applied", lang) % n
|
| 120 |
+
return ('<div style="font-size:12px;color:#4b5563;margin:10px 0 4px">🔐 ' + cap + '</div>'
|
| 121 |
+
'<div style="display:flex;gap:5px">' + cells + '</div>')
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def _metacards(meta, lang):
|
| 125 |
+
tl = len(html.unescape(str(meta.get("title") or "")))
|
| 126 |
+
data = [(_L("제목 길이", "Title len", lang), "%d%s" % (tl, _L("자", "ch", lang))),
|
| 127 |
+
("H1", str(meta.get("h1", 0))),
|
| 128 |
+
(_L("구조화", "Schema", lang), str(meta.get("jsonld", 0))),
|
| 129 |
+
(_L("홈 용량", "Home", lang), "%dKB" % meta.get("size_kb", 0)),
|
| 130 |
+
("alt", "%d%%" % meta.get("alt_cov", 0))]
|
| 131 |
+
cells = "".join('<div style="flex:1;text-align:center;border:1px solid #e4e9ef;border-radius:10px;padding:8px 4px">'
|
| 132 |
+
'<div style="font-size:17px;font-weight:800;color:#2e1065">%s</div>'
|
| 133 |
+
'<div style="font-size:10.5px;color:#6b7280;margin-top:2px">%s</div></div>' % (v, k) for k, v in data)
|
| 134 |
+
return '<div style="display:flex;gap:6px;margin:8px 0">' + cells + '</div>'
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def _upsell(lang):
|
| 138 |
+
return ('<div style="background:linear-gradient(135deg,#6d28d9,#8b5cf6);border-radius:14px;padding:16px 18px;margin:16px 0">'
|
| 139 |
+
'<div style="font-weight:800;font-size:15px;color:#fff">🔓 ' + _L("전사이트 정밀 리포트 — ② 도메인 인증 탭", "Full-site report — ② Domain Verification tab", lang) + '</div>'
|
| 140 |
+
'<div style="font-size:13px;color:#f3e8ff;margin-top:6px">' + _L(
|
| 141 |
+
"이 리포트는 <b>홈 1페이지</b> 기준입니다. 회사/조직 도메인 메일로 가입한 Hugging Face 계정으로 로그인하면 — <b>전사이트 페이지별 이슈 · 구조화데이터 커버리지 · 이메일 위조방지(SPF/DMARC) · 자기모순(주소·전화) 탐지 · 다운로드 리포트</b>까지 받습니다.",
|
| 142 |
+
"This report is based on the <b>home page</b>. Sign in with a Hugging Face account on your company/org domain email to unlock — <b>per-page issues across the whole site · structured-data coverage · email anti-spoofing (SPF/DMARC) · self-contradiction (address/phone) detection · downloadable report</b>.", lang)
|
| 143 |
+
+ '</div></div>')
|
| 144 |
+
|
| 145 |
+
|
| 146 |
def _cards(items, empty, lang):
|
| 147 |
if not items:
|
| 148 |
return '<div style="color:#067647;font-weight:700;padding:8px 0">%s</div>' % empty
|
|
|
|
| 253 |
'<div style="text-align:center;color:#9ca3af;font-size:11px;margin-top:12px">' + _L("서비스: 퀀트ID(QuantID) · 엔진: 치토스(CHITOS)", "Service: QuantID · Engine: CHITOS", lang) + '</div></div>')
|
| 254 |
|
| 255 |
|
| 256 |
+
def build_report(d, deep=False, verified_email=None, lang="ko", show_upsell=False):
|
| 257 |
if d.get("error"):
|
| 258 |
return _note("⚠ " + html.escape(_P(d["error"], lang)), "hi")
|
| 259 |
if d.get("blocked"):
|
|
|
|
| 338 |
+ '<div style="flex:1;border:1px solid #e4e9ef;border-radius:10px;padding:10px"><b style="color:#b54708;font-size:22px">%d</b><div style="font-size:11px;color:#6b7280">%s</div></div>' % (nmid, _L("보완", "Advisory", lang))
|
| 339 |
+ '<div style="flex:1;border:1px solid #e4e9ef;border-radius:10px;padding:10px"><b style="color:#8a6d00;font-size:22px">%d</b><div style="font-size:11px;color:#6b7280">%s</div></div>' % (nlow, _L("낮음", "Low", lang))
|
| 340 |
+ '</div>'
|
| 341 |
+
+ _h2("🛡 " + _L("보안 점검", "Security checks", lang)) + _locks(d.get('locks_on', 0), lang) + _cards(sec, _L("✓ 외부에서 보이는 보안 위생 문제 없음", "✓ No externally visible security-hygiene issues", lang), lang)
|
| 342 |
+
+ _h2("📈 " + _L("SEO 토대 (검색 노출)", "SEO foundation (search visibility)", lang)) + _metacards(meta, lang) + _cards(seo, _L("✓ SEO 기본 요소 양호", "✓ SEO fundamentals are healthy", lang), lang)
|
| 343 |
+ _h2("🤖 " + _L("AI 로봇 접근 (AI 가시성)", "AI crawler access (AI visibility)", lang))
|
| 344 |
+ '<div style="font-size:12.5px;color:#4b5563;margin:4px 0 8px">' + _L("생성형 AI가 당신을 인용하려면 로봇이 먼저 들어와야 합니다.", "For generative AI to cite you, its crawlers must get in first.", lang) + '</div>'
|
| 345 |
+ _traffic(d.get('matrix', {}), lang) + _cards(geo, _L("✓ 기계 판독 신호 양호", "✓ Machine-readability signals are healthy", lang), lang)
|
| 346 |
+ '<div style="font-size:12px;color:#6b7280;margin-top:6px">' + _L("제목", "Title", lang) + ': ' + title80 + ' · H1 ' + str(meta.get('h1', 0)) + ' · ' + _L("구조화", "Schema", lang) + ' ' + str(meta.get('jsonld', 0)) + ' · ' + _L("홈", "Home", lang) + ' ' + str(meta.get('size_kb', 0)) + 'KB · alt ' + str(meta.get('alt_cov', 0)) + '%</div>'
|
| 347 |
+ deep_html
|
| 348 |
+
+ (_upsell(lang) if show_upsell else "")
|
| 349 |
+ _h2("✓ " + _L("잘 되어 있는 점", "What's done well", lang)) + '<div>' + good + '</div>'
|
| 350 |
+ _h2("📌 " + _L("조치 우선순위", "Action priorities", lang)) + _priority(allf, lang)
|
| 351 |
+ '<div style="background:#f4f0ff;border:1px dashed #b89cff;border-radius:12px;padding:14px 16px;margin-top:16px"><b style="color:#6d28d9">' + _L("정밀 진단 안내", "About in-depth diagnosis", lang) + '</b><br><span style="font-size:13px;color:#374151">' + _L(
|
|
|
|
| 386 |
if not (url or "").strip():
|
| 387 |
return _note(_L("진단할 주소를 입력하세요. 예: example.com", "Enter a URL to scan. e.g. example.com", lang), "mid"), None
|
| 388 |
d = _api("/scan", {"url": url})
|
| 389 |
+
return build_report(d, deep=False, lang=lang, show_upsell=True), d
|
| 390 |
|
| 391 |
|
| 392 |
def deep_scan(url, lang, profile: gr.OAuthProfile | None = None, oauth_token: gr.OAuthToken | None = None):
|
|
|
|
| 627 |
|
| 628 |
def relang(label, last_scan, last_deep):
|
| 629 |
lang = "en" if str(label).startswith("Eng") else "ko"
|
| 630 |
+
o1u = gr.update(value=build_report(last_scan, deep=False, lang=lang, show_upsell=True)) if last_scan else gr.update()
|
| 631 |
o2u = gr.update(value=build_report(last_deep["d"], deep=True, verified_email=last_deep.get("email"), lang=lang)) if last_deep else gr.update()
|
| 632 |
return (lang, gr.update(value=label), header_html(lang), desc1(lang), desc2(lang), desc3(lang),
|
| 633 |
area_help(lang), guideline_html(lang), footer_html(lang), o1u, o2u)
|